25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Payments   Humor   Music

Borland C++ XModem send / receive RS-232 serial communications class

SerialCom is a Borland C++ 4.0 drop-in class for MSDOS that provides RS-232 serial communication capabilities, including XModem support in single-file or batch mode.

It uses Borland's DOS-only serial communications functions.

There is also a modem program that uses the SerialCom class.

Other versions: Conversion of this to Windows, as class SWinCommDev.

There are also notes about RS-232 and serial communications.

serialco.cpp

/*	serialco.cpp		11-6-99
	Copyright (C)1996-99, 2006 Steven Whitney.
	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

------
Notes:

handles communications through a serial port with some functions for Hayes modem support,
and can now do xmodem send/receive in single file or batch mode.

1/25/06: In spite of how messy this looks, the smodem program that uses this class worked well at
transferring files in batch mode between my H89 (running Mex.com) and Gateway P5-90,
with occasional glitches. I don't remember what the specific glitches were, but they were
handshaking-related, not program crashes, and it was useful to be sitting at both computers to
restart the transfer. If either side is to operate unattended, this program probably needs
more work.

------
TO DO:

--See http://en.wikipedia.org/wiki/Cyclic_redundancy_check for CRC resources and links.
--Google [xmodem crc gnu] to find suitable GPL equivalent code for CRC calcs.
  Also try Teoma, Clusty.
--copy any new fns here to wserial.cpp and modify them for Windows use there.
--Some of my old programs refer to functions no longer here. Resolve as they're encountered.
--if a FILE fails in xmodem batch receive mode, let sender believe you are still
  receiving it (just don't write anything to disk), but be sure to warn USER
  at end of transfer which file(s) failed to be received. Abandoning the entire batch
  transfer is a needless waste.
--If I ever bring over interrupt driven routines, some methods (and maybe fns) from
  wserial (Windows version) may be required here.
--operator << could be modified for use in a simple pgm that acts as a breakout box,
  continually reading and reporting on status of both COM ports. update display
  only if something changes. However, MSD already does report everything available that
  is of any likely interest. (framing error, etc., and delta-DCD, etc. infrequently
  needed).

------
NOTES:

--Is INT14 inherently too slow for high speed transfers?
--Anticipated uses:
  1)Program sends output to H89 computer FOR TEXT STATUS REPORTS:
	SerialCom H89;
	H89 << string(statustext);		// remember to include \r\n or whatever's needed

  2)Program sends output to H100, WHICH SERVES AS A SECONDARY GRAPHICS DISPLAY:
	int x, y, color;
	SerialCom H100;
	string outstring = tostring(x) + "," + tostring(y) + "," + tostring(color) + "\r";
	H100 << outstring;   			// H100 sets its pixel at (x,y) to color.

*/
#include <bios.h>
#include <dos.h>
#pragma hdrstop

// This program should work as-is in checksum mode. To use CRC mode, I use old functions of
// unknown source that have no notice of authorship or copyright, so there is no one to ask
// for permission to use them, and thus I can't post them here. Those functions are
// available elsewhere on the internet, but it may take some searching.
// There are probably also equivalent routines licensed under the GNU GPL that could be used, but
// there are many CRC-calc routines and determining which one is "equivalent" may be difficult.
//
// crc.cpp is required for this program to calculate Cyclic Redundancy Check bytes.
// It has the following two functions:
// ushort crc_update(ushort& crc, char ch)  // ushort = unsigned short
// ushort crc_finish(ushort& crc)

#define ALLOWCRC		0		// 1 if you provide your own CRC calculation routines

#if (ALLOWCRC)
	#include "c:\bcs\library\crc.cpp"
#else
	// dummy placeholder fns that will allow compilation, but won't actually work.
	ushort crc_update(ushort& crc, char ch) { return 0; }
	ushort crc_finish(ushort& crc) { return 0; }
#endif

#include "c:\bcs\library\filearay.cpp"

//////////////////////////////////////////////////////////////////////////////

#define  RETRYMAX 	20 		// max retrys before abort

#define  SECTORSIZE 128		// must match CPM value
#define  BUFSEC 	255 	// sectors to buffer during XMODEM receipt (32k - 1 sector)
							// (this is the max that ostream.write() can handle)
							// we actually allocate 1 extra to avoid overflow.

#define  TIMEOUT 	(256)	// timeout character, safely above transmittable ASCII

//////////////////////////////////////////////////////////////////////////////
class SerialCom
{
public:
	SerialCom(int c = 0, int b = 1200, int d = 8, int s = 1, int p = 0);

	// functions:
	// port control
	BOOL InitComPort(int,int,int,int,int);  // initialize com port
	BOOL ResetComPort();					// reinit port with existing values

	// status
	friend ostream& operator << (ostream& os, const SerialCom&);	// text status report
	uint PortStatus() const;					// get encoded status of serial port
	string GetStatusText(uint status) const;	// translate encoded status int
	BOOL CharWaiting();
	BOOL CarrierDetect();
	int GetBaud() { return(baud); }

	// receive
	uint ReceiveChar(long secs);			// wait maximum of n seconds for 1 char
	string ReceiveString();                 // build a string from incoming chars
	uint FlushIncoming();

	// send
	uint SendChar(uchar ch);                	// send a char when port can accept it
	void SendString(const string& s); 			// send string to modem
	SerialCom& operator << (const string& s);   // send string to modem

	// file transfer
	int XMODEMReceiveFiles(string& filename, BOOL batch = FALSE);
	int XMODEMSendFiles(string& filename, BOOL batch = FALSE);

	// Hayes modem control
	void HayesReset();                       	// reset Hayes modem
	void HayesCommand(const string& command);  	// issue Hayes modem command
	void HayesResultwait();                     // wait for Hayes result code

	// variables
	enum dispmodes { FULL, BRIEF } displaymode;	// mode for status reports
												// Hayes parameters we need to know:
	BOOL HayesVerbose;                          // verbose result codes?
	BOOL HayesEcho;                             // modem echoes input to it?

	BOOL crcmode;	// should be able to make this local to fns?
					// once it works properly automatically, no need to ask user about it.

protected:
	// functions
	void CalcCRC(ushort& crc, uint ch);
	uint ReadAndCalcCRC(ushort& crc, long secs);
	void SendCRC(ushort crc);

	BOOL UserCancel();

	// variables
	int port;     								// 0 = com1, 1 = com2...
	int baud;
	int databits;
	int stopbits;
	int parity;

	BOOL xoff;		// when TRUE, sending is disabled (not yet used)

};
//----------------------------------------------------------------------------
// constructor
// port parameter here corresponds to com1, com2, etc.
// default is com2, 1200 baud, 8 data, 1 stop, no parity
SerialCom::SerialCom(int c, int b, int d, int s, int p)
{
	HayesVerbose = HayesEcho = TRUE;
	displaymode = FULL;

#if (ALLOWCRC)
	crcmode = TRUE;		// CRC is better than checksum if it's available
#else
	crcmode = FALSE;
#endif

	xoff = FALSE;

	// start with known legal values in case InitComPort() fails
	port     = 1;                    	// com2
	baud     = 1200;					// always usable, even for Hayes300 modem
	databits = 8;
	stopbits = 1;
	parity   = 0;

	// first initialize with the legal values
	InitComPort(port,baud,databits,stopbits,parity);

	// then try to override with the provided ones
	InitComPort(c,b,d,s,p);
}                		//constructor
//----------------------------------------------------------------------------
// initialize com port
// if any parameter is invalid, its value isn't changed.
// if a parameter is valid, its associated member is set to it.
BOOL SerialCom::InitComPort(int c, int b, int d, int s, int p)
{
BOOL ok = TRUE; 			// stays true only if all parameters are valid

if((c >= 0) && (c <= 1))   	// only allow 0,1 for com1, com2
	port = c;
else
{
	cerr << "Invalid com port, ignored.  0 = com1, 1 = com2..." << endl;
	ok = FALSE;
}

char ch = 0;                // communications parameters built in this byte
switch(b)
{
	case 1200: baud = b; ch |= _COM_1200; break;  	// Hayes 300
	case 2400: baud = b; ch |= _COM_2400; break;	// Hayes 2400, H100 Mex?
	case 4800: baud = b; ch |= _COM_4800; break;    // H89 Mex
	case 9600: baud = b; ch |= _COM_9600; break;
	default:
		cerr << "Invalid baud rate, ignored.  Use 1200, 2400, 4800, 9600." << endl;
		ok = FALSE;
		break;
}
switch(d)
{
	case 7: databits = d; ch |= _COM_CHR7; break;
	case 8: databits = d; ch |= _COM_CHR8; break;
	default:
		cerr << "Invalid data bits, ignored.  Use 7 or 8." << endl;
		ok = FALSE;
		break;
}
switch(s)
{
	case 1: stopbits = s; ch |= _COM_STOP1; break;
	case 2: stopbits = s; ch |= _COM_STOP2; break;
	default:
		cerr << "Invalid stop bits, ignored.  Use 1 or 2." << endl;
		ok = FALSE;
		break;
}
switch(p)
{
	case 0: parity = p; ch |= _COM_NOPARITY; break;
	case 1: parity = p; ch |= _COM_ODDPARITY; break;
	case 2: parity = p; ch |= _COM_EVENPARITY; break;
	default:
		cerr << "Invalid parity, ignored.  Use 0(None), 1(Odd), or 2(Even)." << endl;
		ok = FALSE;
		break;
}
_bios_serialcom(_COM_INIT, port, ch);

xoff = FALSE;			// always reenable
return(ok);
}                    	//InitComPort
//----------------------------------------------------------------------------
// reinit port with existing values (in hope of clearing error conditions, etc.)
BOOL SerialCom::ResetComPort()
{
return(InitComPort(port, baud, databits, stopbits, parity));
}
//----------------------------------------------------------------------------
// sends string to serial port.  Returns reference to itself to allow chaining.
// You must convert non-string arguments to string before calling.
// Briefly tried a template version to accomodate any type, without success.
// Note that this is a member, not friend, and doesn't involve ostreams at all.
// #error, as member, order of arguments not significant?: must be global friend?
// Both these should be legal, given SerialCom sc:
// sc << string("Value = ") + tostring(value) + "\n";  			// 1 long string
// sc << string("Value = ") << tostring(value) << string("\n");	// chained output
// This function not yet tested.  For use with H89, H100.
SerialCom& SerialCom::operator << (const string& s)
{
	SendString(s);
	return(*this);
}
//----------------------------------------------------------------------------
// get status of the port
uint SerialCom::PortStatus() const
{
return(_bios_serialcom(_COM_STATUS,port,0));
}						//PortStatus
//----------------------------------------------------------------------------
// returns TRUE if carrier detected
BOOL SerialCom::CarrierDetect()
{
char ch = 0;
return((_bios_serialcom(_COM_STATUS, 0, ch) & (1 << 7)) != 0);
}
//----------------------------------------------------------------------------
// translate provided status to text form.
// i should be the return value from a call to _bios_serialcom()
// port errors seem to be automatically CLEARED by a bioscom() status request,
// so you should use this instead of operator << if you want an existing error analyzed.
// in that situation, (when i is not the result of a call with parameter _COM_STATUS),
// the low byte is garbage, not a valid status word.
string SerialCom::GetStatusText(uint i) const
{
ostrstream os;
if(displaymode == FULL)
{
	os << "Status:" << endl;
	os << "Timeout Error              : " << ((i & (1 << 15)) ? "YES" : "NO") << endl;
	os << "Tx Shift Register Empty    : " << ((i & (1 << 14)) ? "YES" : "NO") << endl;
	os << "Tx Holding Register Empty  : " << ((i & (1 << 13)) ? "YES" : "NO") << endl;
	os << "Break Detected             : " << ((i & (1 << 12)) ? "YES" : "NO") << endl;
	os << "Framing Error              : " << ((i & (1 << 11)) ? "YES" : "NO") << endl;
	os << "Parity Error               : " << ((i & (1 << 10)) ? "YES" : "NO") << endl;
	os << "Overrun Error              : " << ((i & (1 <<  9)) ? "YES" : "NO") << endl;
	os << "Data Ready                 : " << ((i & (1 <<  8)) ? "YES" : "NO") << endl;
	os << endl;
	os << "Carrier Detect             : " << ((i & (1 << 7)) ? "YES" : "NO") << endl;
	os << "Ring Indicator             : " << ((i & (1 << 6)) ? "YES" : "NO") << endl;
	os << "Data Set Ready             : " << ((i & (1 << 5)) ? "YES" : "NO") << endl;
	os << "Clear To Send              : " << ((i & (1 << 4)) ? "YES" : "NO") << endl;
	os << "Carrier Detect Changed?    : " << ((i & (1 << 3)) ? "YES" : "NO") << endl;
	os << "Trailing Edge Ring Detector: " << ((i & (1 << 2)) ? "YES" : "NO") << endl;
	os << "Data Set Ready Changed?    : " << ((i & (1 << 1)) ? "YES" : "NO") << endl;
	os << "Clear To Send Changed?     : " << ((i & 1)        ? "YES" : "NO") << endl;
}
else
{
	os << "TE  SRE  HRE  Brk   FE   PE   OE  Rdy  DCD   RI  DSR  CTS  ^DCD ^RI ^DSR ^CTS";
	os << endl;
	os << ((i & (1 << 15)) ? "Y" : "N") << "    ";
	os << ((i & (1 << 14)) ? "Y" : "N") << "    ";
	os << ((i & (1 << 13)) ? "Y" : "N") << "    ";
	os << ((i & (1 << 12)) ? "Y" : "N") << "    ";
	os << ((i & (1 << 11)) ? "Y" : "N") << "    ";
	os << ((i & (1 << 10)) ? "Y" : "N") << "    ";
	os << ((i & (1 <<  9)) ? "Y" : "N") << "    ";
	os << ((i & (1 <<  8)) ? "Y" : "N") << "    ";
	os << ((i & (1 <<  7)) ? "Y" : "N") << "    ";
	os << ((i & (1 <<  6)) ? "Y" : "N") << "    ";
	os << ((i & (1 <<  5)) ? "Y" : "N") << "    ";
	os << ((i & (1 <<  4)) ? "Y" : "N") << "    ";
	os << ((i & (1 <<  3)) ? "Y" : "N") << "    ";
	os << ((i & (1 <<  2)) ? "Y" : "N") << "    ";
	os << ((i & (1 <<  1)) ? "Y" : "N") << "    ";
	os << ((i & 1)         ? "Y" : "N") << endl;
}
os << "-----------------------------------------------------------------------------" << endl;
os << ends;
string s = os.str();
delete[] os.str();
return(s);
}         				//GetStatusText
//----------------------------------------------------------------------------
// output to a stream, produces a complete status report
// in full-page or brief mode, depending on setting of member displaymode.
ostream& operator << (ostream& os, const SerialCom& s)
{
uint i = s.PortStatus();

// these members NOT obtained from the COM port
os << "Hayes: Verbose = " << (s.HayesVerbose ? "YES" : "NO");
os << "       Echo    = " << (s.HayesEcho ? "YES" : "NO") << endl;
os << "Port, Baud, Data, Stop, Parity = Com" << (s.port + 1) << ", " << s.baud;
os << ", " << s.databits << ", " << s.stopbits << ", " << s.parity << endl;

os << s.GetStatusText(i);
return(os);
}          					//operator <<
//----------------------------------------------------------------------------
// modem has char waiting.  this is useful.  keep it.
BOOL SerialCom::CharWaiting()
{
return((_bios_serialcom(_COM_STATUS, 0, char()) & (1 << 8)) != 0);	// check data ready bit
}                    	//CharWaiting
//----------------------------------------------------------------------------
// This is a key function.  It tries to get a char from the serial port,
// and returns either the char received (0-255) or a value > 255 that indicates
// a failure code.  Any return > 255 indicates a (hopefully) abnormal condition.
// It should be expanded as necessary to handle any situation and return any
// coded status value that ever proves useful.
// It must return a uint (not char) so it can return the status values:
// TIMEOUT = 256, ERROR = (uint)(ERR) = (uint)(-1) = 0xFFFF.
// See Help | bioscom() for status bits.  The ones I test here as those that
// seem caused by *receive* errors are: 0000 1110 0000 0000 = 0xE00.
//
uint SerialCom::ReceiveChar(long secs)
{
if(!CharWaiting()) 		// don't spend time constructing the TTimes unless you have to
{
	// remember the minimum TTime difference or increment is 1 sec.
	TTime endtime;				// initialize to now.
	endtime += secs;			// stop waiting n seconds in the future.
	while(!CharWaiting()) 		// don't spend time constructing the TTime unless you have to
		if(TTime() >= endtime)  // >= allows ReceiveChar(0) to fail immediately if no char
			return(TIMEOUT);	// nothing arrived in time
}
uint ch = _bios_serialcom(_COM_RECEIVE, 0, char()); 		// get the input

// #error there have been transmission errors that 0xE00 didn't catch
if(ch & 0xE00)				// if there was an error bit set,
// if(ch > 255) 			// this is more general, but catches everything, even nonerrors
{
	// full report (slow)
// 	cout << GetStatusText(ch) << "Com receive error, as above." << endl;
	// faster, but still slow
	cout << "Receive Error (hex=): " << hex << ch << dec << endl;
// 	presskey();
// 	ResetComPort();			// enable if it seems ACE must be reset after serious errors
							// (sometimes errors seem to cause hangup or loopback mode)
	return((uint)ERR);		// return generic error code
}
// an eventual possibility, but you probably can't do this here.
// in binary receptions, it would often erroneously turn off the ability to send ACK/NAK.
// if(ch == XOFF)
// 	xoff = TRUE;
// else
// 	if(ch == XON)
// 		xoff = FALSE;
return(ch & 0xFF);          // else return the ascii char, stripped of all status bits
}                    		//ReceiveChar
//----------------------------------------------------------------------------
// accumulate chars from the com port into a string
string SerialCom::ReceiveString()
{
string rbuf;
uint ch;

// while chars are available, append them to the string.  it WILL append error chars!
while((ch = ReceiveChar(1)) != TIMEOUT)
	rbuf += (char)ch;
return(rbuf);
}                 		//ReceiveString
//----------------------------------------------------------------------------
// get and discard incoming characters until a 1 sec silence (or an error).
// returns # of chars flushed
uint SerialCom::FlushIncoming()
{
uint i = 0;

// both these methods assume (maybe wrongly) that we CANNOT get ahead of incoming chars.
// if we can, then it will exit the wait loop prematurely.
// while(CharWaiting())
// {
// 	// new method (no wait allowed, or only enough to execute the code):
// 	if(ReceiveChar(0) == TIMEOUT)
// 		break;
// 	else
// 		i++;
//
// 	// old method:
// 	// allowing 1 sec might hang here due to remote retries at 1 sec intervals?
// 	if(ReceiveChar(1) != TIMEOUT)
// 		i++;
// }

// you usually want to wait out a sector data block, for which this should be perfect.
while(ReceiveChar(1) != TIMEOUT)	// wait for a 1 second silence
	i++;

return(i);
}						//FlushIncoming
//----------------------------------------------------------------------------
// This is a key function.  It tries to send a char through the serial port,
// and returns either the char sent (0-255) or a value > 255 that indicates
// a failure code.  Any return > 255 indicates a (hopefully) abnormal condition.
// It should be expanded as necessary to handle any situation and return any
// coded status value that ever proves useful.
// It must return an int (not char) so it can return the status values:
// TIMEOUT = 256, ERROR = (uint)(ERR) = (uint)(-1) = 0xFFFF.
// See Help | bioscom() for status bits.  The ones I test here as those that
// seem caused by *send* errors are: 1000 0000 0000 0000 = 0x8000. (only timeout)
//
// you can test using: if(SendChar(ch) != ch)
//
// you would test xoff here, but it introduces many complications: you must build
// a backlog of unsent chars for when you CAN send.
//
// something else you could do here is test for WAITING chars; if any, read
// them and append to a buffer (or string).  ReceiveChar would return chars from the
// buffer (until empty) before reading any new ones from the port.  this would
// minimize lost chars during long periods where you are sending only.
uint SerialCom::SendChar(uchar ch)
{
// if you impose a time limit, it should probably go here.
// but Help|bioscom makes it seem that timeout is already handled by hardware,
// and appropriately reported in status bit.  (maybe uses MSDOS mode setting?)

// wait until holding register empty
while( (_bios_serialcom(_COM_STATUS, 0, char()) & (1 << 13)) == 0);

uint i = _bios_serialcom(_COM_SEND, 0, ch);		// get status
if(i & 0x8000)									// if there was any error bit set,
{
// 	ResetComPort();			// enable if it seems ACE must be reset after serious errors
							// (sometimes errors seem to cause hangup or loopback mode)
// 	if(i & 0x8000)			// explicit test for timeout, if other tests added above.
		return(TIMEOUT);	// return timeout
// 	return((uint)ERR);		// return value for other errors
}
return(ch);					// if successful, return the original char
}							// SendChar
//----------------------------------------------------------------------------
// send all chars from a string to modem.
void SerialCom::SendString(const string& s)
{
	for(int i = 0 ; i < s.length() ; i++)
		SendChar(s[i]);
}                   	//SendString
//////////////////////////////////////////////////////////////////////////////
// crc functions
//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
// calculates crc or checksum, depending on which mode is active.
void SerialCom::CalcCRC(ushort& crc, uint ch)
{
if(crcmode)
	crc_update(crc, ch);
else
	crc = (crc + ch) & 0xff;		// this chops it down to 1 char size: correct?
}                  		//CalcCRC
//----------------------------------------------------------------------------
// read a char and tally it into checksum/CRC.
// returns the same as ReceiveChar, passed through.
uint SerialCom::ReadAndCalcCRC(ushort& crc, long secs)
{
uint ch = ReceiveChar(secs);
if(ch < 256)							// only if received without error
	CalcCRC(crc, ch);
return(ch);
}                  		//ReadAndCalcCRC
//----------------------------------------------------------------------------
// Sends out crc or checksum, depending on which mode is active.
void SerialCom::SendCRC(ushort crc)
{
if(crcmode)
{
	SendChar((crc >> 8) & 0xFF); 	// send CRC high byte
	SendChar(crc & 0xFF); 			// send CRC low byte
}
else
	SendChar(crc); 					// send checksum byte
}                  		//SendCRC
//----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
// XMODEM transfer functions
//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
// returns TRUE if user has cancelled with CTLX
BOOL SerialCom::UserCancel()
{
if(csts())
	if(ci() == CTLX)			// if it's not, you've lost the keystroke
	{
		cout << "++ Transfer cancelled by you ++ " << endl;
		return(TRUE);
	}
return(FALSE);
}                		//UserCancel
//----------------------------------------------------------------------------
// gets one or more files from remote system.
// on entry, filename is the name of a file, or desired destination PATH if batch mode.
// returns the number of files successfully received.
int SerialCom::XMODEMReceiveFiles(string& filename, BOOL batch)
{
int filecount = 0;				// number of files successfully received
uint moderequests = 0;

// sector chars buffer, with an extra sector, just in case
// below here, you must break to function end, or delete[] bigbuf at every return point
char* bigbuf = new char[(BUFSEC + 1) * SECTORSIZE];

string path;					// desired destination PATH for received files.
if(batch && filename.length())
{
	path = filename;
	if(path.length() == 1)  	// drive only, turn "D" to "D:" and use default subdir
		path += ':';
	else						// ensure longer path is terminated by a : or \
	{
		char ch = path[path.length() - 1];
		if((path.length() > 2) && (ch != '\\'))		// don't change "D:" to "D:\"
			path += '\\';
	}
}
ResetComPort();	 	// hope that it clears error conditions
// FlushIncoming();	// discard any incoming garbage

while(1)							// while(there are more files to receive)
{
	BOOL bad = FALSE;				// if TRUE, whole procedure aborts.
	BOOL done = FALSE;				// whether various sections are done

	//----------------------------------------------------------------------------
	// if batch mode, receive filename.  if not batch, we already have it.
	if(batch)
	{
		cout << "Awaiting filename     " << endl;
		filename.remove(0);							// fresh start

		// I can only make the filename transfer work (with Mex) in checksum mode.
		// Mex responds as though it supports crc mode for filename, but even in crc mode
		// it only accepts 1 byte as a reply, so it really uses only checksum for filenames.
		// My routines do support either mode.
		// for dealing with Mex, crcmode is always FALSE inside here, but restored TRUE later
		// for the data transfer.
		crcmode = FALSE;
		while(!bad && !done)	// exits: (1)good filename or (2)too many errors
		{
			// every good char clears error count because the only important thing
			// is whether there are so many consecutive errors that you must abort.
			uint errors = 0;				// error count
			uint ch = ReceiveChar(1);		// get filename character
			switch(ch)
			{
				case TIMEOUT:				// trap problem flags
					// on first entry to this switch, it WILL timeout because we
					// haven't requested crc/checksum mode yet.  Do it now, and keep
					// count, switching to checksum if necessary, as before.
					// if the timeout occurs BETWEEN letters, our eventual NAK might
					// just get it going again?.
					if(++moderequests > RETRYMAX)
					{
						if(crcmode)			// prepare for retries with checksum
						{
							cout << "No CRC mode ACK, switching to checksum " << endl;
							crcmode = FALSE;
							moderequests = 0;
						}
						else
						{
							cout << "Checksum mode request also failed. Aborting." << endl;
							bad = TRUE;
						}
					}
					SendChar(crcmode ? 'C': NAK);   // request CRC or checksum mode from sender
					break;

				case ACK: 					// this is sender's reply to our 'C'/NAK
					break;                  // do nothing more

				case 'u': 				// I think this means start over from beginning
										// 'u' is safe: all filename letters are uppercase
					cout << "Retrying filename from beginning" << endl;
					errors = 0;						// error count
					filename.remove(0);				// fresh start
					SendChar(crcmode ? 'C': NAK);   // request CRC or checksum mode from sender
					break;

				case CTLZ:					// CTLZ signals filename END.
				{
					filename += (uchar)ch;	// the CTLZ goes into it also
					ushort crc = 0;        	// cumulative checksum or CRC
					for(int i = 0 ; i < filename.length() ; i++)
						CalcCRC(crc, filename[i]);
					if(crcmode)
						crc_finish(crc);
					SendCRC(crc); 			// send checksum or CRC
					ch = ReceiveChar(1); 	// wait for ACK
					if(ch == ACK)
						done = TRUE;
					else					// it will send 'u' if wrong
					{
						cout << "Checksum error in filename" << endl;
						if(++errors > RETRYMAX)
							bad = TRUE;
						else
						{
							cout << "Retrying filename from beginning" << endl;
							errors = 0;						// error count
							filename.remove(0);				// fresh start
							SendChar(crcmode ? 'C': NAK);   // request mode again
						}
					}
					break;
				}							// end case CTLZ

				case EOT:					// no more filenames: we're completely done
					delete[] bigbuf;
					cout << "Batch receive complete" << endl;
					return(filecount);

				case ERR:					// no diff: checksum must catch any error
				default:                    // any valid char appends to filename
					filename += (uchar)ch;	// accumulate filename
					// cout << (uchar)ch;
					SendChar(ACK);  		// acknowledge EACH CHAR of the filename
					break;
			}								// end switch
			if(UserCancel())    			// user quit
			{
				SendChar(CAN); 	// tell sender to cancel, too (put IN UserCancel?)
				bad = TRUE;
			}
		}									// while(!bad and !done)
		if(filename.length())
		{
			// filename always arrives as 11 chars (8 + 3), no dot, with filename and ext
			// each padded with trailing spaces if necessary, + CTLZ = 12 chars.
			size_t i;
			for(i = 0 ; i < filename.length() ; i++)	// strip parity bits, if any
				filename[i] = filename[i] & 0x7F;
			if(filename[filename.length() - 1] == CTLZ)	// strip the ending CTLZ
				filename.remove(filename.length() - 1);
			filename.insert(8,".");						// insert a period
			while((i = filename.find(" ")) != NPOS)		// remove all spaces
				filename.remove(i,1);
		}
	}										// end if(batch) {get filename}
	if(!filename.length())	// originally empty, OR we accidentally emptied it
	{
		if(!bad)						   	// on abort, empty filename is normal
			cout << "Error: Filename is an empty string." << endl;
		bad = TRUE;
	}
	if(bad)
		break;								// go to fn end for cleanup and return

	done = FALSE;							// for reuse later
	//----------------------------------------------------------------------------
	// we have a legal filename.  Make sure it's ok to use the file.

	if(batch)
		filename.prepend(path);							// prepend output path
	filename.to_upper();                                // upper case for messages
	ofstream outfile(filename.c_str(),ios::noreplace | ios::binary);
	if(!outfile)
	{
		if(yesno("Output file " + filename + " already exists.  Overwrite"))
		{
			outfile.open(filename.c_str(),ios::binary);
			if(!outfile)		// user said overwrite, but it still failed.
			{
				cout << "File creation error: " << filename << endl;
				break;			  		// go to fn end for cleanup and return
			}
		}
		else
		{
			cout << "Resolve conflict and try again." << endl;
			break;				  		// go to fn end for cleanup and return
		}
	}
	//----------------------------------------------------------------------------
	// file setup ok.  Now download its sectors.  The theory of operation here
	// is that at each point there is a right answer and multiple wrong ones,
	// handled by ifs and switches.  As long as all is ok, you go deeper and
	// deeper into the nested blocks.  Success is at the deepest level.
	// On error, you can set a flag if necessary, and MUST break out to
	// redo the whole sector (or quit entirely).

	cout << "Receiving: " << filename << endl;
// 	cout << "Awaiting Initial Sector... " << endl;

	char* goodchars = bigbuf;	// ptr to 1 char beyond last known-good char in bigbuf
	uint sectorsreceived = 0;   // number of sectors successfully received
	uint tries = 0;
	moderequests = 0;
	crcmode = TRUE;				// even if it failed above, try it again
	while(!bad && !done)		// wait for SOH, which signals sector # is coming next
	{
		ushort crc = 0;									// crc accumulator
		uint ch = ReceiveChar(4);
		switch(ch)
		{
			case SOH:	// this is what we want, and this block receives 1 sector
			{
				uint currentsector = ReceiveChar(1);	// current sector number
				uint seccmp = ReceiveChar(1);			// sector number complement (~)
				if((currentsector + seccmp) == 255)     // it's correct IF they total 255
				{
					// if it's one more than the previous sector, it's correct!
					// 0xff probably handles 255-0 or 255-1 transition, but is it right?
					// here, they must both be handled as bytes.
					if((currentsector & 0xff) == ((sectorsreceived + 1) & 0xff))
					{
						// good sectors will overwrite; any error should leave error msg
						// so next sector starts on a new line.
						// (sectorsreceived + 1) can count past 255.
						cout << "\rSector #"
							 << setw(5) << (sectorsreceived + 1) << ":  ";
						crc = 0;						// reset crc

						// receive exactly one sector of bytes, tentatively appending
						// them into the sectors buffer (pending crc validation).
						char* endpoint = goodchars + SECTORSIZE;
						for(char* s = goodchars ; s < endpoint ; )
						{
							// top priority is catching chars as fast as they come in.
							// bad ones of any type should be caught by crc error later.
							// I also turned off stack overflow check in the project.
							// and incrementing s here while we have it instead of at loop end
							// may save a fetch of s.

							// reliable, and imposes time limit
							// *(s++) = (uchar)ReceiveChar(1);

							// even faster, but program WILL hang up if chars stop coming.
							// ok for local (2 computers in the same room) use,
							// since you can type ^X locally,
							// then type the needed chars on the H89 to cause loop exit,
							// whereupon your ^X gets trapped and causes abort.
							*(s++) = (uchar)_bios_serialcom(_COM_RECEIVE, 0, char());
						}
						// calculate crc on the entire sector during transmission pause
						for(s = goodchars ; s < endpoint ; s++)
							CalcCRC(crc, *s);
						if(crcmode)					// get crc info from sender
						{
							ReadAndCalcCRC(crc, 1);
							ReadAndCalcCRC(crc, 1);
						}
						else
							crc = crc - ReceiveChar(1);
						// with either method, if crc is ZERO, sector is OK.
						// this block is total success for the sector!
						if(!crc)
						{
							tries = 0;						// reset tries
							sectorsreceived++;
							// goodchars won't be changed again until we know if next char
							// is SOH or EOT (unless buffer is full and written below),
							// so we can use it as the end of text marker in EOT:
							goodchars = endpoint;			// appended chars are ok
							// if buffer full, do disk write.
							// sectorsreceived (a uchar) is ok to use if BUFSEC < 256
							if((sectorsreceived % BUFSEC) == 0)
							{
								// buffer is full, so write it all
								outfile.write(bigbuf, BUFSEC * SECTORSIZE);
								if(outfile)
								{
									// both pointers restart at buffer beginning
									goodchars = bigbuf;
									// if sender times out while we write, it will
									// resend the same sector.  Hang here until it's done,
									// then fall through and acknowledge
									FlushIncoming();
								}
								else				// serious error: just quit
								{
									cout << "Error writing file. Aborting" << endl;
									bad = TRUE;
									break;			// break from switch case
								}
							}
							SendChar(ACK);		// signal that we got the sector ok
						}
						else		// crc error, send NAK for retry
						{
							if(++tries > RETRYMAX)
							{
								cout << "Too many retries.  Aborting." << endl;
								bad = TRUE;
								break;			// break from switch case
							}
							else
							{
								cout << "Sector CRC/checksum error" << endl;
								FlushIncoming();	// wait for silence
								SendChar(NAK);      // request retry
							}
						}
					}			// end if(this sector is 1 more than previous sect)
					else		// sector number RECEIVED ok, but it is the wrong ONE
					{
						if(currentsector == (sectorsreceived & 0xff))
						{
							cout << "Receiving and ignoring duplicate sector"
								 << sectorsreceived << endl;
							FlushIncoming();	// wait for silence on the line
							SendChar(ACK);      // pretend we received it
						}
						else 	// sector # received ok, but it's totally wrong number
						{
							cout << "Wrong sector #. Lost synchronization. Aborting." << endl;
							bad = TRUE;
							break;
						}
					}
				}				// end if((currentsector + seccmp) == 255)
				else			// transmission error in the sector # itself
				{
					if(++tries > RETRYMAX)
					{
						cout << "Too many retries.  Aborting." << endl;
						bad = TRUE;
						break;			// break from switch case
					}
					else		// may have been the right number, so retry it.
					{
						cout << "Sector # header error.  Retrying." << endl;
						FlushIncoming();	// wait for silence
						SendChar(NAK);      // request retry
					}
				}
				break;
			}								// end case SOH:

			case CTLX:						// cancel came instead
				bad = TRUE;
				break;

			case EOT:						// no more sectors: we're done.
				outfile.write(bigbuf,(int)(goodchars - bigbuf));	// write the final block
				if(outfile)					// whole file is a success!
				{
					filecount++;
					cout << "File receive complete" << endl << endl;
					done = TRUE;
				}
				else						// unrecoverable error: just quit
				{
					cout << "Error writing file, Aborting" << endl;
					bad = TRUE;
				}
				SendChar(ACK);	// report OK (even if not ok, sender can't help us)
				break;

			case TIMEOUT:   				// took too long
				// on first entry to this switch, it WILL timeout because we
				// haven't requested crc/checksum mode yet.  Do it now, and keep
				// count, switching to checksum if necessary, as before.
				// sender does not acknowledge our request: just starts sending with SOH.
				// if the timeout occurs BETWEEN sectors, our eventual NAK might
				// just get it going again with the previous sector.
				if(++moderequests > 5)
				{
					if(crcmode)			// prepare for retries with checksum
					{
						cout << "No CRC mode acknowledge, switching to checksum " << endl;
						crcmode = FALSE;
						moderequests = 0;
					}
					else
					{
						cout << "Checksum mode request also failed. Aborting." << endl;
						bad = TRUE;
					}
				}
				SendChar(crcmode ? 'C': NAK);   // request CRC or checksum mode from sender
				break;

			case ERR:
			default:	// any other non-SOH char is completely wrong.  All you can do
						// is wait through the sector data and send NAK to retry, which will
						// happen automatically because the next sector #s won't match.
				cout << "Received 0x" << hex << ch << dec << ", not SOH" << endl;
				// The 1 second silence wait here is important because the binary
				// data stream probably DOES have SOHs in it that you don't want to trap.
				FlushIncoming();
				break;
		}									// end switch(SOH or other chars)
		if(UserCancel())    				// user quit
			bad = TRUE;
	}						// end while(!bad && !done)
	outfile.close();		// close file so we can delete it if necessary
	if(bad)
	{
		SendChar(CAN); 		// unsure if sender will actually cancel
		cout << "Deleting partial file: " << filename << endl;
		unlink(filename.c_str());                   // delete it
		batch = FALSE;		// a bad file cancels the whole batch; you can't recover
	}     					// if you try to continue, you get garbage for a filename.
	if(!batch)				// if not batch mode, we're done after 1 file
		break;
}							// while(1)
delete[] bigbuf;
return(filecount);
}                      		//XMODEMReceiveFiles
//----------------------------------------------------------------------------
// on entry, pathname is the name of the file to send (full path, maybe with wildcards)
// returns the number of files successfully sent.
int SerialCom::XMODEMSendFiles(string& pathname, BOOL batch)
{
int filecount = 0;				// number of files successfully sent

FileArray FileList(FALSE);
FileList.AddFile(pathname);				// if wildcards, adds all matching files
while(1)
{
	BOOL done = FALSE;					// whether various sections are done
	string filename;  					// the filename in OUR format
	ifstream infile; 					// the file we're sending

	//----------------------------------------------------------------------------
	// make sure the file we're about to send exists and can be read.
	// if not, skip and do next in list (if any).
	while(FileList.GetItemsInContainer())       // if one fails, try next (if any)
	{
		filename = FileList.GetNext();  		// the filename in OUR format
		infile.open(filename.c_str(), ios::binary);
		if(infile)
		{
			done = TRUE;
			break;
		}
		else
			cout << "Cannot open " << filename << endl;
	}
	if(!done)				// if end of batch or we couldn't get a good file
	{
		if(batch)      		// this is the normal return point for batch mode
		{
			ReceiveChar(10);		// wait until receiver starts sending NAK for next file
			SendChar(ACK); 			// fake acknowledge their mode request
			SendChar(EOT); 			// final EOT = end of batch
			cout << "Batch send complete" << endl;
		}
		return(filecount);			// either way, nothing left to send.
	}
	done = FALSE;					// for reuse
	//----------------------------------------------------------------------------
	// if batch mode, send filename.  if not batch, they already have it.
	if(batch)
	{
		//----------------------------------------------------------------------------
		// translate the name to weird transmission format (and with path stripped)
		FilePathParser fp(filename);
		string sendfile = fp.File;     			// copy to strings
		string sendext = fp.Ext;
		while(sendfile.length() < 8)			// pad filename out to 8 chars
			sendfile += " ";
		if(sendext.length())					// remove period from extension
			if(sendext[0] == '.')
				sendext.remove(0,1);
		while(sendext.length() < 3)				// pad extension out to 3 chars
			sendext += " ";
		string sendfilename = sendfile + sendext + (uchar)CTLZ;	// final form
		sendfilename.to_upper();                           		// MUST be upper case
		//----------------------------------------------------------------------------
		// send the filename to the receiver, using error checking
		while(!done)
		{
			cout << "Awaiting filename NAK/'C'" << endl;
			ushort crc = 0;        					// checksum or CRC
			while(1)								// get mode request from receiver
			{
				uint j = ReceiveChar(10);
				if((j == 'C') || (j == NAK))		// only one or the other is legal
				{
					crcmode = (j == 'C');		   	// 'C' = CRC, NAK = checksum
					SendChar(ACK);	 				// WE acknowledge their mode request
					// precalc crc, depending on mode.
					// our value to compare against only changes if receiver changes mode.
					for(int i = 0 ; i < sendfilename.length() ; i++)
						CalcCRC(crc, sendfilename[i]);
					break;                          // we got what we needed...
				}
				if(UserCancel())
				{
					SendChar(CAN); 		// tell receiver to cancel, too (put IN UserCancel?)
					return(filecount);
				}
			}
			BOOL bad = FALSE;				// if TRUE, filename send restarts
			// send only up to the CTLZ: the required response is different for it.
			for(int i = 0 ; i < (sendfilename.length()- 1) ; )
			{
				SendChar(sendfilename[i]);  // send filename character
				uint ch = ReceiveChar(2);	// get ACK (hopefully)
				if(ch == ACK)				// receiver's reply to each filename character
					i++;					// prepare to send next one
				else    					// anything else is wrong
				{
					cout << "Retrying filename from beginning" << endl;
					bad = TRUE;				// restart from beginning
					break;                  // quit looping
				}
			}                               // end for(all chars except last)
			if(!bad)                        // so far, so good...
			{
				SendChar(CTLZ);				// send the CTLZ (it's always the last)
				if(crcmode)                 // get crc or checksum
				{
					ushort crchi = (ushort)ReceiveChar(2); 	// get crc high byte
					ushort crclo = (ushort)ReceiveChar(1); 	// get crc low byte
					if(((crchi << 8) + crclo) == crc)
						done = TRUE;
				}
				else
				{
					if((ushort)ReceiveChar(2) == crc) 		// get checksum
						done = TRUE;
				}
			}
			if(UserCancel())    			// user quit
			{
				SendChar(CAN); 		// tell receiver to cancel, too (put IN UserCancel?)
				return(filecount);
			}
			SendChar(done ? ACK : 'u');	// means OK or redo from start
		}								// while(!done)
	}									// end if(batch)
	//----------------------------------------------------------------------------
	// send the file sectors
	cout << endl;
	cout << "Sending: " << filename << endl;
	cout << "Awaiting NAK/'C'" << endl;
	while(1)								// get mode request from receiver
	{
		uint j = ReceiveChar(10);
		if((j == 'C') || (j == NAK))		// only one or the other is legal
		{
			crcmode = (j == 'C');		   	// 'C' = CRC, NAK = checksum
			break;                          // we got what we needed...
		}
		if(UserCancel())
			return(filecount);
	}
	int tries = 0;							// # of times current sector was tried (failed)
	uint currentsector = 1;					// # of sector being sent

	// these may be needed for batch mode, when one break won't get completely out of loop
	BOOL bad = FALSE;						// if TRUE, whole procedure aborts.
	done = FALSE;							// whether various sections are done
	while(!bad && !done)
	{
		// As soon as we get receiver's 'C' or NAK, we send (no wait or acknowledgment):
		// SOH, AND the 2 sector count chars, AND the sector data, AND the crc/checksum.
		// The only complaint the receiver can make is to wait for the
		// sector end and send NAK (for retry).
		// Under all circumstances SENDER only aborts if retry limit is exceeded.
		// (Receiver can't request abort.)

		// start a sector.  good sectors overwrite.  bad leaves error msg and starts new line.
		cout << "\rSector #" << setw(5) << currentsector << ":  ";
		SendChar(SOH);
		SendChar((uchar)currentsector);			// strips high byte for 0-255 only
		SendChar(~((uchar)currentsector));      // complement used for error check
		ushort crc = 0;
		uchar ch;								// file char to be sent
		for(int i = 0 ; i < SECTORSIZE ; i++)
		{
			// method slow, but we're the sender; shouldn't matter
			if(infile.get(ch))		// MSDOS will read and buffer larger chunks for us.
			{
				CalcCRC(crc, ch); 	// accumulate crc
				SendChar(ch);       // send the char
			}
			else					// reached end of file, but the protocol requires
			{						// whole sectors, so pad to end.
				// CPM pads with whatever data is actually after the CTLZ in the sector.
				// we pad with CTLZ.  If file ended at a sector boundary, there
				// will be NO ctlz pads, but Mex will probably add a ctlz when it writes
				// last sector.
				ch = CTLZ;			// so CPM knows where exact EOF is.
				CalcCRC(crc, ch); 	// accumulate crc
				SendChar(ch);       // send the char
			}
		}							// end for(send 1 sector)
		if(crcmode)					// finishing calcs for crc only
			crc_finish(crc);
		SendCRC(crc);              	// always send the crc or checksum
		if(ReceiveChar(10) == ACK)  // if it was ok,
		{
			tries = 0;								// reset retry counter
			currentsector++;						// start a new sector (if any more)
			// peek prevents sending an empty extra sector: if the file ended on an exact
			// sector boundary in the for() above, infile hasn't failed yet.
			if(!infile || (infile.peek() == EOF))	// finished file
			{
				SendChar(EOT);                      // send EOT instead of SOH
				if(ReceiveChar(10) == ACK)			// need response
				{
					cout << "File send complete" << endl << endl;
					filecount++;
					done = TRUE;
				}
				else
				{
					cout << "No ACK of End of File! Aborting." << endl;
					bad = TRUE;
				}
				break;								// break to redo while(1)
			} 										// end if(finished file)
		}
		else								// receiver had errors: must retry same sector.
		{
			if(++tries > RETRYMAX)
			{
				cout << "Too many sector retries.  Aborting." << endl;
				break;				// break to fn end and quit
			}
			else
				cout << "Received NAK not ACK.  Retrying sector." << endl;
		}
		if(UserCancel())
			bad = TRUE;
	}							// while(!bad && !done)
	if(!batch)					// if not batch mode, we're done after 1 file
		break;					// break to fn end
}								// while(1)
return(filecount);				// normal return point for nonbatch mode
}								//XMODEMSendFiles
//----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
// Hayes Modem functions
//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
// reset Hayes modem.  There will be at least a 2 sec. wait.
void SerialCom::HayesReset()
{
	SendString("\r");  		// if modem is in an error state, this starts fresh
	FlushIncoming();		// while chars come in, read and discard them

	SendString("ATZ\r");  	// reset
	FlushIncoming();		// while chars come in, read and discard them
	HayesVerbose = HayesEcho = TRUE;
}                  		//HayesReset
//----------------------------------------------------------------------------
// send command string to Hayes modem, and wait for result code.
void SerialCom::HayesCommand(const string& command)
{
	FlushIncoming();				// while chars come in, read and discard them

	// V commands take effect immediately, so change setting before the command
	// so resultwait will work properly.
	if(command.contains("V"))		// V1, V0, or Vblank means V0
		HayesVerbose = FALSE;
	if(command.contains("V1"))     	// but V1 overrides
		HayesVerbose = TRUE;

	SendString(command + "\r");		// issue the command
	HayesResultwait();				// wait for the result code

	// E commands don't affect the command line that contains them.  If echo
	// was on, the command was echoed, so don't change settings until after.
	if(command.contains("E"))		// E1, E0, or Eblank means E0
		HayesEcho = FALSE;
	if(command.contains("E1"))     	// but E1 overrides
		HayesEcho = TRUE;

}                  		//HayesCommand
//----------------------------------------------------------------------------
// wait for the \r from modem's NON-verbose result code string
// or for the second /n from a verbose result code string
void SerialCom::HayesResultwait()
{
uint ch;
int count = 0;
if(HayesVerbose)     			// verbose result = \r\nRESULT\r\n
{
	while(1)
	{
		ch = ReceiveChar(1);
// 		cerr << "(" << ch << " " << (iscntrl(ch) ? ' ' : (char)ch) << ") ";
		if(ch == '\n')
			if(++count == 2)
				break;
	}
}
else               				// nonverbose result = digit\r
{
	// If input is being echoed, our command-terminating CR comes back,
	// so we have to wait for the next one.  Not a problem in verbose mode,
	// because in that case Resultwait waits for LF, not CR.
	int crneed = HayesEcho ? 2 : 1;
	while(1)
	{
		ch = ReceiveChar(1);
// 		cerr << "(" << ch << " " << (iscntrl(ch) ? ' ' : (char)ch) << ") ";
		if(ch == '\r')
			if(++count == crneed)
				break;
	}
}
delay(100); 	// modem isn't quite ready for input the instant it sends OK
}                    	//HayesResultwait
//----------------------------------------------------------------------------
// 						end class SerialCom
//////////////////////////////////////////////////////////////////////////////

smodem.cpp

A modem program that uses the SerialCom class, which provides most of its functionality.

/*	smodem.cpp			10-24-99
	Copyright (C)1999 Steven Whitney.
	Published under GNU GPL (General Public License) Version 3, with ABSOLUTELY NO WARRANTY.

------
TO DO:

when batch send reports that it is finished, restate the user's command, for reference.
Ex: "Finished sending batch *.DOC"

P5-90 computer:
	receive works at 4800 with few errors,
	It CAN receive with no errors at 9600 when Windows is not running.
	Send at 4800 is borderline, 9600 NO: receiver's problem?
	Mex can't RECEIVE at 9600 with any other pgm, either.
	still doesn't seem right that a 90mh computer with an ACE probably rated
	at 38,400 or higher can't keep up with a 2mh computer at 9600!

a Windows version (for file transfer only) would be convenient,
with file selection for send from a dialog box.

sometime, test xmodem transfer between 2 running copies of terminal.exe
(one on each COM port) at 9600.  can they do it?

the occasional error resolved by retry is rare.
most errors are spectacular, multiple, and catastrophic.

during some receives there have been sector-long pauses without error msgs (and w/o errors).
H89 calculating crc or something?

A possible enhancement would be to allow using this as a remote terminal to H89 or H100
by intercepting H19 terminal codes and translating locally to the appropriate actions.
(Graphics chars a particular challenge.) But low priority, not actually worth doing.

------
Notes:

--Only testing can tell if the xmodem protocol is properly implemented.
--all usual CTL keys except ^X are now ALT, so control keys can be passed through and sent.
--error checking works: when errors occur, received file is correct.

*/
#include <dos.h>
#include <math.h>
#include <classlib\time.h>
#include "c:\bcs\my.h"
#pragma hdrstop

#include "c:\bcs\mylib.cpp"
#include "c:\bcs\library\serialco.cpp"

//////////////////////////////////////////////////////////////////////////////

#define  BUFFSIZ 	0x4000 	// size of text CAPTURE buffer (16k)

//////////////////////////////////////////////////////////////////////////////
// Globals
//----------------------------------------------------------------------------

// here so ShowHelp can display their values
BOOL capture	= FALSE;
BOOL echo 		= FALSE;
BOOL PrintFlag 	= FALSE;

SerialCom sc(0, 4800, 8, 1, 0);

// used in multiple locs
char *capturebuf = 0;		// text capture buffer
uint TxtPtr = 0;            // number of bytes in capture buffer

//----------------------------------------------------------------------------
void SetBaudRate()
{
static int baudtable[4] = {1200,2400,4800,9600};

int newbaud = sc.GetBaud();
userinput("[1200,2400,4800,9600] Enter baud rate ", newbaud);
for(int i = 0 ; i < 4 ; i++)
	if(newbaud == baudtable[i])
	{
		sc.InitComPort(0, newbaud, 8, 1, 0);
		cout << sc << endl;
		presskey();
		return;
	}
cout << "Invalid baud rate.  No change." << endl;
}                     	//SetBaudRate
//----------------------------------------------------------------------------
// help for terminal mode
void ShowTermHelp()
{
	cout << "--------------------------------------------------" << endl;
	cout << "TERMINAL MODE HELP:" << endl;
	cout << "ALT+A    Go to Command Mode" << endl;
	cout << "ALT+E    Echo, now " << (echo ? "ON" : "OFF") << endl;
	cout << "ALT+H    Help for terminal mode" << endl;
	cout << "ALT+P    Printer, now " << (PrintFlag ? "ON" : "OFF") << endl;
	cout << "ALT+R    Reset modem" << endl;
	cout << "CTL+X    Cancel send/recv" << endl;
	cout << endl;
}
//----------------------------------------------------------------------------
void DoCommand()
{
string filename;
BOOL batchmode 	= FALSE;
string cmdline;
while(1)
{
	ShowTermHelp();
	cout << "--------------------------------------------------" << endl;
	cout << "You are now in COMMAND MODE:" << endl;
	cout << endl;
	cout << "C        (" << (capture ? "ON " : "OFF") << ") Capture received data to buffer" << endl;
	cout << "B        Change baud rate" << endl;
	cout << "K        Save captured data" << endl;
	cout << "Q        Exit program" << endl;
	cout << "R        Receive file" << endl;
	cout << "S        Send file" << endl;
	cout << "T        Enter terminal mode" << endl;
	cout << endl;
	cout << "Command:  ";
	getline(cin,cmdline,'\n');  	// get user command
	cmdline.to_upper();
	char cmd = 0;            		// reset each pass
	if(cmdline.length())
		cmd = cmdline[0];  			// first char is the single char command
	switch(cmd)
	{
		// no ALT key cases: getline() above probably can't read them anyway
		case 'B':						// change the baud rate
			SetBaudRate();
			break;

		case 'C':						// toggle text capture
			capture = !capture;
			cout << "Capture now" << (capture ? "ON" : "OFF, ")
				 << (BUFFSIZ - TxtPtr) << " bytes free" << endl;
			break;

		case 'K':						// keep text buffer
			if(!TxtPtr)
				cout << "Nothing to save" << endl;
			else
			{
				userinput("Save as what file", filename);

				// ok for now, but you should ask for a name at pgm start,
				// then append to that file whenever the buffer fills up.
				// and eliminate Keep command.
				ofstream outfile(filename.c_str(), ios::binary | ios::app);
				if(outfile)
				{
					outfile.write(capturebuf, TxtPtr);
					capture = FALSE;
					TxtPtr = 0;
				}
				else
					cout << "File Write Error.  Try again." << endl;
			}
			break;

		case 'R':								// receive file
			batchmode = FALSE;
			if((cmdline.length() > 1) && (toupper(cmdline[1]) == 'B'))	// RB allowed
				batchmode = TRUE;
			else
				batchmode = yesno("Batch mode");
// 			sc.crcmode = yesno("CRC mode");
			userinput(batchmode ? "Destination path" : "Receive what file", filename);
			sc.XMODEMReceiveFiles(filename, batchmode);
			presskey();
			filename.remove(0);					// a nuissance when it's held over
			break;

		case 'S':								// send file
			batchmode = FALSE;
			if((cmdline.length() > 1) && (toupper(cmdline[1]) == 'B'))	// SB allowed
				batchmode = TRUE;
			else
				batchmode = yesno("Batch mode");
			userinput("Send what file (* and ? ok)", filename);
			if(!batchmode)
				if(filename.contains("?") || filename.contains("*"))
				{
					cout << "You need batch mode for wildcards.  Try again." << endl;
					break;
				}
			sc.XMODEMSendFiles(filename, batchmode);
			presskey();
			filename.remove(0);
			break;

		case 'Q':                       		// quit program
			if(!yesno("Quit program"))
				break;
			if(capturebuf)
				delete[] capturebuf;
			exit(0);

		case 'T':						 		// enter terminal mode
			cout << "Entering terminal mode..." << endl;
			return;

		default:
			cout << "Invalid command, try again" << endl;
			break;
	}
}							// while(1)
}							// DoCommand
//----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
void main()
{
string::set_case_sensitive(0);
string::set_paranoid_check(1);

capturebuf = new char[BUFFSIZ];
ofstream printer;					// used when echoing to printer

sc.displaymode = SerialCom::BRIEF;	// or FULL;
sc.ResetComPort();
sc.FlushIncoming();

uint ch = ALTA; 					// so first entry displays menu
while(1)							// THIS LOOP IS TERMINAL MODE
{
	if(csts())			 	 			// get any char at keyboard
		ch = ci();
	if(ch)
		switch(ch)							// terminal mode commands
		{
			case ALTA:
				DoCommand(); 	    		// this is command mode
				break;

			case ALTH:						// HELP
				ShowTermHelp();
				break;

			case ALTP:						// toggle echo to printer
				if(PrintFlag)    			// if it's on, turn off
					printer.close();
				if(!PrintFlag)				// if it's off, turn ON
					printer.open("PRN:");
				PrintFlag = !PrintFlag;   	// now change setting to match
				break;

			case ALTQ:						// quit
				if(!yesno("Quit program"))
					break;
				if(capturebuf)
					delete[] capturebuf;
				exit(0);

			case ALTE:						// local echo of keyboard data
				echo = !echo;
				break;

			case ALTR:						// Reset modem
				sc.ResetComPort();
				break;

			default:                    	// send user-typed text through modem
				sc.SendChar(ch);
				if(echo)
				{
					// outgoing should be diff color from incoming, but it requires constream.
					// since serialcom outputs to cout, the 2 cursor locs may differ,
					// garbling screen display.
					cout << TranslateControls((uchar)ch);   // show translated
// 					cout << (uchar)ch;                      // show normal

					// you want both sides of dialog in the capture buffer.
					// if local echo is ON, then presumably remote is not echoing,
					// so we must add our own output to the capture buffer.
					// if local echo is OFF, it gets written there when it comes back below.
					if(capture)
						if(TxtPtr < BUFFSIZ)
							capturebuf[TxtPtr++] = ch;
						else
							cout << "Capture buffer overflow" << endl;
				}
				if(PrintFlag)	 						// and maybe to printer
					printer << ((ch == CR) ? '\n' : ch);// file write will expand to crlf
				break;
		}
	ch = 0;									// reset it
	if(sc.CharWaiting())					// incoming chars ready?
	{
		uint inchar = sc.ReceiveChar(1); 	// get it (no wait: we know there's one waiting)

		// #error useful during testing, but note that it translates <CR><LF>
		// to those strings rather than couting them.
		cout << TranslateControls((uchar)inchar);       // show translated
// 		cout << (uchar)inchar;							// show normal
		cout << "=" << hex << inchar << dec << " ";
		if(capture)
			if(TxtPtr < BUFFSIZ)
				capturebuf[TxtPtr++] = inchar;
			else
				cout << "Capture buffer overflow" << endl;
		if(PrintFlag)
			printer << ((inchar == CR) ? '\n' : inchar);
	}
}						// end while
}						// main
//----------------------------------------------------------------------------

 

 

Valid HTML 4.01 Transitional Valid CSS
Yahoo! Search
Search the web Search this site
View content labeling at ICRA.