Work at SourceForge, help us to make it a better place! We have an immediate need for a Support Technician in our San Francisco or Denver office.

Close

#1053 improvement of Win32 serial communication error reports

obsolete: 8.2.1
closed-fixed
nobody
2
2001-04-18
2000-10-26
Anonymous
No

OriginalBugID: 3368 RFE
Version: 8.2.1
SubmitDate: '1999-11-05'
LastModified: '1999-11-23'
Severity: LOW
Status: Released
Submitter: techsupp
ChangedBy: hobbs
OS: Windows 95
FixedDate: '1999-11-23'
FixedInVersion: 8.3b1
ClosedDate: '2000-10-25'

Name:

Rolf Schroedter

CVS:

RCS: @(#) $Id: tclWinSerial.c,v 1.7.2.1 1999/09/24 22:49:14 hobbs Exp $

Comments:

The patch has been tested under Win95 and WinNT at different baud rates.

If [fconfigure chan -lasterror] does not fit into the Tcl philosophy it

could be an option to have only the error notification (EIO).

Then it would be nice to have a mechanism of reporting operating system

depending error details to Tcl.

DesiredBehavior:

The current implementation of the Win32 serial port driver does not

report communication errors to Tcl.

Especially if file events are used, communication errors are ignored and

cleared by the call to ClearCommError().

For the user this looks like Tcl is swallowing incoming bytes without

notification.

But this is not Tcl, but the Win32 serial system driver.

Typical errors are

FRAME # wrong baud rate or data/stop bits or noise on the

serial line

PARITY # wrong parity settings or noisy line

RXOVER # receiver buffer overrun

The propoesed patch for tclWinserial.c does the following:

1. On Win32 communication errors during the evCheckProc an read/write

fileevent is queued and the error is saved.

2. The next read/write operation returns a file-I/O error (EIO).

3. The user can then optionally call

fconfigure chan -lasterror

which returns error details, e.g.

FRAME RXOVER

Notice that -lasterror is a read-only option and is therefore not

returned by an unnamed

fconfigure chan

Patch:

*** tclWinSerial.c.old Fri Sep 24 22:49:14 1999

--- tclWinSerial.c.new Tue Oct 12 17:52:50 1999

***************

*** 49,54 ****

--- 49,61 ----

#define SERIAL_DEFAULT_BLOCKTIME 10 /* 10 msec */

/*

+ * Define Win32 read/write error masks returned by ClearCommError()

+ */

+ #define SERIAL_READ_ERRORS ( CE_RXOVER | CE_OVERRUN | CE_RXPARITY \

+ | CE_FRAME | CE_BREAK )

+ #define SERIAL_WRITE_ERRORS ( CE_TXFULL )

+

+ /*

* This structure describes per-instance data for a serial based channel.

*/

***************

*** 66,71 ****

--- 73,82 ----

int writable; /* flag that the channel is readable */

int readable; /* flag that the channel is readable */

int blockTime; /* max. blocktime in msec */

+ DWORD error; /* pending error code returned by

+ * ClearCommError() */

+ DWORD lastError; /* last error code, can be fetched with

+ * fconfigure chan -lasterror */

} SerialInfo;

typedef struct ThreadSpecificData {

***************

*** 343,349 ****

SerialEvent *evPtr;

int needEvent;

ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);

- DWORD cError;

COMSTAT cStat;

if (!(flags & TCL_FILE_EVENTS)) {

--- 354,359 ----

***************

*** 370,383 ****

*/

if( infoPtr->watchMask & (TCL_WRITABLE | TCL_READABLE) ) {

! if( ClearCommError( infoPtr->handle, &cError, &cStat ) ) {

/*

* Look for empty output buffer. If empty, poll.

*/

if( infoPtr->watchMask & TCL_WRITABLE ) {

if( ((infoPtr->flags & SERIAL_WRITE) != 0) && \

! (cStat.cbOutQue == 0) ) {

/*

* allow only one fileevent after each callback

*/

--- 380,397 ----

*/

if( infoPtr->watchMask & (TCL_WRITABLE | TCL_READABLE) ) {

! if( ClearCommError( infoPtr->handle, &infoPtr->error, &cStat ) ) {

/*

* Look for empty output buffer. If empty, poll.

*/

if( infoPtr->watchMask & TCL_WRITABLE ) {

+ /*

+ * force fileevent after serial write error

+ */

if( ((infoPtr->flags & SERIAL_WRITE) != 0) && \

! ( (cStat.cbOutQue == 0) || \

! (infoPtr->error & SERIAL_WRITE_ERRORS) ) ) {

/*

* allow only one fileevent after each callback

*/

***************

*** 394,400 ****

*/

if( infoPtr->watchMask & TCL_READABLE ) {

! if( cStat.cbInQue > 0 ) {

infoPtr->readable = 1;

needEvent = 1;

}

--- 408,418 ----

*/

if( infoPtr->watchMask & TCL_READABLE ) {

! /*

! * force fileevent after serial read error

! */

! if( (cStat.cbInQue > 0) ||

! (infoPtr->error & SERIAL_READ_ERRORS) ) {

infoPtr->readable = 1;

needEvent = 1;

}

***************

*** 566,589 ****

SerialInfo *infoPtr = (SerialInfo *) instanceData;

DWORD bytesRead = 0;

DWORD err;

- DWORD cError;

COMSTAT cStat;

*errorCode = 0;

/*

* Look for characters already pending in windows queue.

* This is the mainly restored good old code from Tcl8.0

*/

! if( ClearCommError( infoPtr->handle, &cError, &cStat ) ) {

/*

* Check for errors here, but not in the evSetup/Check procedures

*/

! if( cError != 0 ) {

! *errorCode = EIO;

! return -1;

}

if( infoPtr->flags & SERIAL_ASYNC ) {

/*

--- 584,612 ----

SerialInfo *infoPtr = (SerialInfo *) instanceData;

DWORD bytesRead = 0;

DWORD err;

COMSTAT cStat;

*errorCode = 0;

+ /*

+ * Check if there is a CommError pending from SerialCheckProc

+ */

+ if( infoPtr->error & SERIAL_READ_ERRORS ){

+ goto commError;

+ }

+

/*

* Look for characters already pending in windows queue.

* This is the mainly restored good old code from Tcl8.0

*/

! if( ClearCommError( infoPtr->handle, &infoPtr->error, &cStat ) ) {

/*

* Check for errors here, but not in the evSetup/Check procedures

*/

! if( infoPtr->error & SERIAL_READ_ERRORS ) {

! goto commError;

}

if( infoPtr->flags & SERIAL_ASYNC ) {

/*

***************

*** 633,638 ****

--- 656,666 ----

TclWinConvertError(GetLastError());

*errorCode = errno;

return -1;

+ commError:

+ infoPtr->lastError = infoPtr->error; /* save last error code */

+ infoPtr->error = 0; /* reset error code */

+ *errorCode = EIO; /* to return read-error only once */

+ return -1;

}

/*

***************

*** 665,670 ****

--- 693,708 ----

*errorCode = 0;

+ /*

+ * Check if there is a CommError pending from SerialCheckProc

+ */

+ if( infoPtr->error & SERIAL_WRITE_ERRORS ){

+ infoPtr->lastError = infoPtr->error; /* save last error code */

+ infoPtr->error = 0; /* reset error code */

+ *errorCode = EIO; /* to return read-error only once */

+ return -1;

+ }

+

/*

* Check for a background error on the last write.

* Allow one write-fileevent after each callback

***************

*** 926,931 ****

--- 964,970 ----

infoPtr->readable = infoPtr->writable = 0;

infoPtr->blockTime = SERIAL_DEFAULT_BLOCKTIME;

+ infoPtr->lastError = infoPtr->error = 0;

/*

* Files have default translation of AUTO and ^Z eof char, which

***************

*** 1014,1019 ****

--- 1053,1097 ----

/*

*----------------------------------------------------------------------

*

+ * SerialErrorStr --

+ *

+ * Converts a Win32 serial error code to a list of readable errors

+ *

+ *----------------------------------------------------------------------

+ */

+ static void

+ SerialErrorStr(error, dsPtr)

+ DWORD error; /* Win32 serial error code */

+ Tcl_DString *dsPtr; /* Where to store string */

+ {

+ if( (error & CE_RXOVER) != 0) {

+ Tcl_DStringAppendElement(dsPtr, "RXOVER");

+ }

+ if( (error & CE_OVERRUN) != 0) {

+ Tcl_DStringAppendElement(dsPtr, "OVERRUN");

+ }

+ if( (error & CE_RXPARITY) != 0) {

+ Tcl_DStringAppendElement(dsPtr, "RXPARITY");

+ }

+ if( (error & CE_FRAME) != 0) {

+ Tcl_DStringAppendElement(dsPtr, "FRAME");

+ }

+ if( (error & CE_BREAK) != 0) {

+ Tcl_DStringAppendElement(dsPtr, "BREAK");

+ }

+ if( (error & CE_TXFULL) != 0) {

+ Tcl_DStringAppendElement(dsPtr, "TXFULL");

+ }

+ if( (error & ~(SERIAL_READ_ERRORS | SERIAL_WRITE_ERRORS)) != 0) {

+ char buf[TCL_INTEGER_SPACE + 1];

+ wsprintfA(buf, "%d", error);

+ Tcl_DStringAppendElement(dsPtr, buf);

+ }

+ }

+

+ /*

+ *----------------------------------------------------------------------

+ *

* SerialGetOptionProc --

*

* Gets a mode associated with an IO channel. If the optionName arg

***************

*** 1103,1111 ****

Tcl_DStringAppendElement(dsPtr, buf);

}

if (valid) {

return TCL_OK;

} else {

! return Tcl_BadChannelOption(interp, optionName, "mode pollinterval");

}

}

--- 1181,1200 ----

Tcl_DStringAppendElement(dsPtr, buf);

}

+ /*

+ * get option -lasterror

+ * option is readonly and returned by [fconfigure chan -lasterror]

+ * but not returned by unnamed [fconfigure chan]

+ */

+

+ if ( (len > 1) && (strncmp(optionName, "-lasterror", len) == 0) ) {

+ valid = 1;

+ SerialErrorStr(infoPtr->lastError, dsPtr);

+ }

+

if (valid) {

return TCL_OK;

} else {

! return Tcl_BadChannelOption(interp, optionName, "mode pollinterval lasterror");

}

}

*** open.n.old Wed Jul 21 15:06:10 1999

--- open.n.new Tue Oct 12 18:33:16 1999

***************

*** 152,157 ****

--- 152,165 ----

time interval between checking for events throughout the Tcl

interpreter (the smallest value always wins). Use this option only if

you want to poll the serial port more often than 10 msec (the default).

+ .TP

+ \fB\-lasterror

+ .

+ This query-only option, is only available on Windows for serial ports.

+ In case of a serial communication error, \fBread\fR or \fBputs\fR

+ returns a general Tcl file I/O error.

+ \fBfconfigure -lasterror\fR can be called to get a list

+ of error details (e.g. FRAME RXOVER).

.VE

.VS

PatchFiles:

tclWinSerial.c

open.n

Added to 8.3.
-- 11/23/1999 hobbs

Discussion

  • Brent B. Welch
    Brent B. Welch
    2000-10-26

    • priority: 5 --> 2
    • status: open --> closed-fixed
     
  • Don Porter
    Don Porter
    2001-04-18

    • labels: 104246 --> 27. Channel Types