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

RS-232 serial communication C++ class for Windows

A drop-in class for Windows that provides some RS-232 serial communication capabilities.

It has the routines, and maybe the potential, for the xmodem capabilities that the DOS version has, but as far as I remember, those routines remain untested in this Windows version.

The class appears not to be specific to Borland's ObjectWindows Library, so it might be generally usable in Windows without OWL.

Other versions: MSDOS-only version class SerialCom.

There are also some other notes about RS232 and serial communications.

wserial.h

/*	wserial.h			11-6-99
	Copyright (C)1996-99 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.

------

A communications device for Windows, simple communications through a serial port,
with some functions for Hayes modem support.

Its organization is from serialco.cpp (DOS), its comm methods from Wincode.cpp (morse code pgm).
It is for Win16 ONLY. Win32s does not support serial communications fns at all.

2006:	THIS WAS BASICALLY AN UNCOMPLETED CONVERSION OF THE MSDOS VERSION. WORK ON IT
		ENDED AS SOON AS IT WAS ABLE TO DO THE MINIMAL TASK IT WAS NEEDED FOR.
1999:	Works, but is very rough, and Hayes fns not reviewed at all.
		It is sufficient for sending status reports to com1 from within Win pgms.

Currently (that was in 1999) being greatly expanded, temporarily through EasyWin,
and now has some fns that may be illegal in Windows.

#include this file into any program where you want to use it, after all
other standard #includes and "my.h".  It contains code and cannot be precompiled.

------
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.
--if remote sends stream of 'C'/NAK, they all go in buffer (not lost as before).
  As soon as we get one, empty entire buffer?  Or ReceiveLastChar().  What?
--add fn uint CharsWaiting(), returns # in input queue instead of just BOOL.
--maybe add fn uint ReceiveCharArray(char* s, uint maxchars, uint minchars = 1);
  to fill s with at most maxchars from buffer, or do nothing if at least minchars aren't waiting.
  I think this idea was for more efficiently reading 80-char blocks for xmodem
  routines.  But test 1 at a time, first.  May be sufficient.

*/
#ifndef __WSERIAL_H
#define __WSERIAL_H

#pragma hdrstop

#include "c:\bcs\my.h"

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

#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 SWinCommDev
{
public:
	// if you overflow the WINDOWS transmit buffer, output is truncated.
	// the giant outbuf HERE is the only way I found to allow output of large text blocks;
	// it works fine, but if you overflow THIS buf, you'll still have the same problem.
	// c = com1,2, etc. (starts at 1=com1, NOT 0=com1 that port uses)
	// default baud 9600 works for output to "localcom2" (serial port loop).
	SWinCommDev(uint c = 1, uint b = 9600, uint d = 8, uint s = 1, char p = 'n',
				uint inbuffersize = 60000U, uint outbuffersize = 60000U);
	~SWinCommDev();

	friend ostream& operator << (ostream& os, const SWinCommDev&);	// status report
	friend SWinCommDev& operator << (SWinCommDev&, const string&); 	// string to modem

	// functions
	BOOL InitComPort(uint c = 1, uint b = 9600, uint d = 8, uint s = 1, char p = 'n',
				uint inbuffersize = 60000U, uint outbuffersize = 60000U); // initialize port
	BOOL Send(const string& s); 	// string to modem
	BOOL Send(uint ch); 			// char to modem
	string ReceiveString();         // build a string from incoming chars
	uint ReceiveChar(long secs);	// get next char (if any) from queue
	void FlushIncoming();
	BOOL CharWaiting();

	// will need mods for higher baud
	// uint GetBaud() { return(((dcb.BaudRate >> 8) != 0xFF)? (dcb.BaudRate & 0xFF00) : 0); }
	uint GetBaud() { return(dcb.BaudRate); }

	unsigned getstatus() const;						// get status of port
	void HayesReset();                     		  	// reset Hayes modem
	void HayesCommand(const string& command);  		// issue Hayes modem command
	void HayesResultwait();                     	// wait for Hayes result code

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

	// variables
	enum dispmodes { FULL, BRIEF } displaymode;	// mode for status reports
	string lasterror;

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

	// Hayes parameters we need to know:
	BOOL HayesVerbose;                          // verbose result codes?
	BOOL HayesEcho;                             // modem echoes input to it?

protected:
	// functions
	BOOL errorreport(const string&);		// sets lasterror, returns FALSE

	// 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..., -1=invalid
	uint inbufsize;
	uint outbufsize;

	DCB dcb;								// communications parameters structure
	COMSTAT comstat;						// status, incl. queue sizes

};
//----------------------------------------------------------------------------
// 						end class SWinCommDev
//////////////////////////////////////////////////////////////////////////////
#endif			// __WSERIAL_H

wserial.cpp

/*	wserial.cpp				11-6-99
	Copyright (C)1996-99 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.

	Code for class SWinCommDev.

*/
#include "c:\bcs\library\wserial.h"
#pragma hdrstop

// This program MIGHT 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

//////////////////////////////////////////////////////////////////////////////
// class SWinCommDev
//----------------------------------------------------------------------------
// constructor
// port parameter HERE is 1=com1, 2=com2, etc.
SWinCommDev::SWinCommDev(uint comport, uint baud, uint databits, uint stopbits, char parity,
						uint inbsize, uint outbsize)
{
HayesVerbose = HayesEcho = TRUE;
displaymode = FULL;
port = -1;					// for dtor, in case a valid one is never opened
InitComPort(comport, baud, databits, stopbits, parity, inbsize, outbsize);
}                    		//constructor
//----------------------------------------------------------------------------
// destructor
// flushes and closes: you MUST make sure object stays in scope until output is finished.
SWinCommDev::~SWinCommDev()
{
if(port >= 0)
	CloseComm(port);
}                        	//destructor
//----------------------------------------------------------------------------
// initialize com port
BOOL SWinCommDev::InitComPort(uint comport, uint baud, uint databits, uint stopbits,
								char parity, uint inbsize, uint outbsize)
{
inbufsize = inbsize;
outbufsize = outbsize;

GetCommError(comport,&comstat);		// this should reset if there was a previous error

string portname = string("COM") + tostring(comport);
port = OpenComm(portname.c_str(),inbufsize,outbufsize); 	// open the port
if(port < 0)
	return(errorreport(tostring(port) + " Failure opening " + portname));

string parms = portname + ":"                      // set up its comm parameters
						+ tostring(baud) + ","
						+ tostring(parity) + ","
						+ tostring(databits) + ","
						+ tostring(stopbits);
int err = BuildCommDCB(parms.c_str(), &dcb);
if(err < 0)
	return(errorreport(tostring(err) + " BuildCommDCB"));

// set additional comm parameters
dcb.fRtsDisable = FALSE;
dcb.fDtrDisable = FALSE;

dcb.fOutxCtsFlow = TRUE;
dcb.fOutxDsrFlow = TRUE;

dcb.fDtrflow = TRUE;
dcb.fRtsflow = TRUE;

dcb.fInX = TRUE;
dcb.fOutX = TRUE;

// better set xoffLim (see) and maybe others


err = SetCommState(&dcb);  		// initialize the port WITH the parameters
if(err < 0)
	return(errorreport(tostring(err) + " SetCommState"));

return(TRUE);
}              		      	//init
//----------------------------------------------------------------------------
BOOL SWinCommDev::errorreport(const string& errorstring)
{
lasterror = errorstring;
return FALSE;
}                    		//errorreport
//----------------------------------------------------------------------------
BOOL SWinCommDev::CharWaiting()
{
GetCommError(port,&comstat);		// gets port status
return(comstat.cbInQue > 0);		// incoming chars ready?
}                       	//CharWaiting
//----------------------------------------------------------------------------
// send all chars from a string to modem.
BOOL SWinCommDev::Send(const string& s)
{
if(port < 0)
	return(errorreport("Invalid Port"));

GetCommError(port,&comstat);				// unlocks port if frozen by an error
uint avail = outbufsize - comstat.cbOutQue;
if(avail < 20)								// no room to write anything
	return(errorreport("Buffer Overflow"));

string temp;               					// so we don't modify s, which might be used elsewhere
if(avail < s.length())						// only enough room for an error message
	temp = "\r** Truncated **\r";      		// 17 chars
else      									// there is room for entire output string
	temp = LFtoCRLF(s);

// flush doesn't seem like a very good idea!
// FlushComm(port,1);						// 1=flush the receive buffer

int result = WriteComm(port, temp.c_str(), temp.length());	// send the string
if(result < 0)
{
	// do other stuff
	int i = GetCommError(port,&comstat);	// unlocks port if frozen by an error
	return(errorreport(tostring(i) + " Comm Write Failure")) ;
}
return TRUE;
}                   		//Send
//----------------------------------------------------------------------------
// send a char to modem.
BOOL SWinCommDev::Send(uint ch)
{
return(Send(string((uchar)ch)));
}
//----------------------------------------------------------------------------
// get next char from the receive queue
// returns the char, or TIMEOUT if none, or ERR if error at port.
uint SWinCommDev::ReceiveChar(long /* secs */ )
{
uchar ch = 0;								// the char we will return
if(port < 0)
	return((uint)ERR);

// may need to make this a loop, and use secs as a wait time.
int count = ReadComm(port, &ch, 1);			// 0 = no more chars in queue, neg = error
if(count <= 0)
{
	int i = GetCommError(port,&comstat);	// unlocks port if frozen by an error
	if(i)                                   // if 0, no error: queue was just empty.
	{
		lasterror = tostring(i) + " Comm Read Failure";
		return((uint)ERR);
	}
	return(TIMEOUT);
}
return(ch);
}                 		//ReceiveChar
//----------------------------------------------------------------------------
// accumulate ALL waiting chars from the com port into a string, and return it.
// #error max string length is 64k.  if chars arrive fast enough, s could overflow.
string SWinCommDev::ReceiveString()
{
string s;				// the string we will return; starts empty, may stay empty
if(port < 0)
	return s;
char buf[81] = {0}; 	// receive the chars in up to 80-byte chunks
int count;				// # of chars actually read

// 0 = no more chars in queue, neg = error
while((count = ReadComm(port,buf,sizeof(buf)-1)) > 0) 	// get the string
{
	buf[count] = 0;		// terminate the chunk
	s += buf;			// append new chunk
}
return(s);
}                 		//ReceiveString
//----------------------------------------------------------------------------
void SWinCommDev::FlushIncoming()
{
	FlushComm(port, 1);		// flush receive queue
}
//----------------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////////
// crc functions
//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
// calculates crc or checksum, depending on which mode is active.
void SWinCommDev::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 SWinCommDev::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 SWinCommDev::SendCRC(ushort crc)
{
if(crcmode)
{
	Send((crc >> 8) & 0xFF);			// send CRC high byte,
	Send(crc & 0xFF);					// CRC low byte
}
else
	Send(crc);	 						// send checksum byte
}                  		//SendCRC
//----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
// XMODEM transfer functions
//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
// returns TRUE if user has cancelled with CTLX
BOOL SWinCommDev::UserCancel()
{

#if 0
// #error unfinished conversion from DOS
if(kbhit())
	if(getch() == CTLX)			// if it's not, you've lost the keystroke
	{
		cout << "++ Transfer cancelled by you ++ " << endl;
		return(TRUE);
	}
#endif // 0

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 SWinCommDev::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;
						}
					}
					Send(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
					Send(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
							Send(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;
					Send(ACK);  			// acknowledge EACH CHAR of the filename
					break;
			}								// end switch
			if(UserCancel())    			// user quit
			{
				Send(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)
	{
		break;	// temporary
#if 0
// #error unfinished conversion from DOS
		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
		}
#endif // 0
	}
	//----------------------------------------------------------------------------
	// 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.
							// also turned off stack overflow check in project.
							// and incrementing s here while we have it instead of 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 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.

							// #error this will be ReceiveCharArray with an array of 80
							// provided, to get them all.

							*(s++) = ReceiveChar(1);
						}
						// 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
								}
							}
							Send(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
								Send(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
							Send(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
						Send(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;
				}
				Send(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;
					}
				}
				Send(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)
	{
		Send(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 SWinCommDev::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
			Send(ACK); 					// fake acknowledge their mode request
			Send(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
					Send(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())
				{
					Send(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) ; )
			{
				Send(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...
			{
				Send(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
			{
				Send(CAN); 		// tell receiver to cancel, too (put IN UserCancel?)
				return(filecount);
			}
			Send(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 << ":  ";
		Send(SOH);
		Send((uchar)currentsector);			// strips high byte for 0-255 only
		Send(~((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
				Send(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
				Send(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
			{
				Send(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
//----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
// #error anything after here is either not yet converted to Windows or in need of review
//----------------------------------------------------------------------------
// get status of the port
// see GetCommError, COMSTAT, GetCommState, DCB
uint SWinCommDev::getstatus() const
{
return(0);
}          			//getstatus
//----------------------------------------------------------------------------
// output to a stream, produces a complete status report
// in full-page or brief mode, depending on setting of member displaymode.
// 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 does report everything available that
// is of any likely interest.  (framing error, etc., and delta-DCD, etc. infrequently
// needed).
ostream& operator << (ostream& os, const SWinCommDev& /* s */ )
{
os << "operator << was called, is not yet implemented in Windows." << endl;
return os;

#if 0
unsigned i = s.getstatus();

// 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;

// COM port status
if(s.displaymode == s.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;
return(os);
#endif // 0

}          					//operator <<
//////////////////////////////////////////////////////////////////////////////
// Hayes modem control
//----------------------------------------------------------------------------
// wait for the \r from modem's NON-verbose result code string
// or for the second /n from a verbose result code string
void SWinCommDev::HayesResultwait()
{
if(port < 0)
	return;					// nothing more to do if it fails.
char combuf = {0};
int count = 0;
if(HayesVerbose)     			// verbose result = \r\nRESULT\r\n
{
	while(1)
	{
		ReadComm(port, &combuf, 1);
		if(combuf == '\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)
	{
		ReadComm(port, &combuf, 1);
		if(combuf == '\r')
			if(++count == crneed)
				break;
	}
}
TTime referencetime;
while(TTime() == referencetime);	// modem isn't ready for input the instant it sends OK
}                    	//HayesResultwait
//----------------------------------------------------------------------------
// reset Hayes modem.  There will be at least a 2 sec. wait.
void SWinCommDev::HayesReset()
{
if(port < 0)
	return;					// nothing more to do if it fails.
Send("\rATZ\r");
HayesVerbose = HayesEcho = TRUE;
HayesResultwait();
}                  		//HayesReset
//----------------------------------------------------------------------------
// send command string to Hayes modem, and wait for result code.
void SWinCommDev::HayesCommand(const string& command)
{
if(port < 0)
	return;					// nothing more to do if it fails.

// while leftover chars come in, read and discard them
// don't use while(getcomchar()) because it always produces a 1-sec. wait
// However, this line here will probably only read 1 char because the return
// will == 0 before another one has time to come in.
FlushComm(port,1);							// 1=flush the receive buffer

// 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;

Send(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;

}                  		//initmodem
//----------------------------------------------------------------------------
// sends string to serial port.  Returns reference to itself to allow chaining.
// Briefly tried a template version to accomodate any type, without success.
// Note that this doesn't involve ostreams at all.
// Both these should be legal, given SWinCommDev 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.
// made a friend because order is signif.
SWinCommDev& operator << (SWinCommDev& sc, const string& )
{
// if(sc.port >= 0)
// 	sc.Send(s);
return(sc);
}
//----------------------------------------------------------------------------
// 						end class SWinCommDev
//////////////////////////////////////////////////////////////////////////////

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2009 Steven Whitney. Last modified 03/15/2009.