SerialPort.st
author Claus Gittinger <cg@exept.de>
Mon, 03 Sep 2012 22:23:42 +0200
changeset 2818 94d67df5c00d
parent 2751 d62cad0013dd
child 2820 45ee5437c35b
permissions -rw-r--r--
compilable with tcc

"
 COPYRIGHT (c) 2006 by eXept Software AG
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
"{ Package: 'stx:libbasic2' }"

NonPositionableExternalStream subclass:#SerialPort
	instanceVariableNames:'portName baudRate stopBitsType parityType dataBits inFlowCtrlType
		outFlowCtrlType xOnChar xOffChar'
	classVariableNames:'DefaultPortName'
	poolDictionaries:''
	category:'Streams-External'
!

!SerialPort primitiveDefinitions!
%{

#include "stxOSDefs.h"

#ifdef WIN32

/* this is to catch uses of those - there should be none */
# undef __BEGIN_INTERRUPTABLE__
# undef __END_INTERRUPTABLE__
# ifdef __TCC__
#  define __BEGIN_INTERRUPTABLE__ xxxx
#  define __END_INTERRUPTABLE__ yyyy
# else
#  define __BEGIN_INTERRUPTABLE__ ..
#  define __END_INTERRUPTABLE__ ..
# endif

# define WRAP_STDIO

# define PORT_FROM_FD(fd)     (_get_osfhandle(fd))
# define closePort(port)      closeHandle(port)
# define SERIALPORT           HANDLE

#else /* not WIN32 */

# define PORT_FROM_FD(fd)     (fd)
# define closePort(port)      close(port)
# define SERIALPORT           int

#endif /* WIN32 */

# define PORT_FROM_FILE(f)             (PORT_FROM_FD(fileno(f)))
# define PORT_FROM_FILE_OBJECT(f)      (PORT_FROM_FILE(__FILEVal(f)))

#include <stdio.h>
#include <errno.h>

/*
 * on some systems errno is a macro ... check for it here
 */
#ifndef errno
 extern errno;
#endif

#ifdef USE_H_ERRNO
# ifndef h_errno
 extern h_errno;
# endif
#endif

#ifdef DEBUG
# define DBGPRINTF(x)    { if (__debugging__) console_printf x; }
#else
# define DBGPRINTF(x)    /* as nothing */
#endif

#ifndef TRUE
# define TRUE   1
#endif
#ifndef FALSE
# define FALSE  0
#endif
#ifndef WIN32
typedef int BOOL;
#endif


%}
! !

!SerialPort primitiveVariables!
%{
static int __debugging__ = 0;
%}
! !

!SerialPort class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 2006 by eXept Software AG
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    This class provides access to serial ports.

    Arguments/Parameters/Instvars:

	portName                String                  device name
							    WIN32:  COM0, COM1, ...
							    Unix:   /dev/cua0, ...


	baudRate                Integer                 9600, 19200, ...
	stopBitsType            Symbol                  #stop1, #stop1_5 or #stop2
	parityType              Symbol                  #odd, #even or #none
	dataBits                Integer                 5, 6, 7, 8
	inFlowCtrlType          Symbol                  #xOnOff, #hardware or #none
	outFlowCtrlType         Symbol                  #xOnOff, #hardware or #none
	xOnChar                 Integer | Character
	xOffChar'               Integer | Character
"
!

examples
"
    example (get help info from an nntp server):
									[exBegin]
    |serialPort|

    serialPort := SerialPort new setPortName:(SerialPort defaultPortName).
    serialPort open.
    serialPort close.
									[exEnd]

									[exBegin]
    |serialPort|

    serialPort := SerialPort new setPortName:'COM5'.
    serialPort open.
    serialPort nextPutAll:'hello world'.
    serialPort cr.
    serialPort close.
									[exEnd]

									[exBegin]
    |serialPort|

    serialPort := SerialPort new setPortName:'COM5'.
    serialPort open.
    Transcript showCR:(serialPort nextAvailable:100).
    serialPort close.
									[exEnd]
"
! !

!SerialPort class methodsFor:'instance creation'!

new
    "create a serial port"

    ^ super new buffered:false
!

portName:portName
   baudRate:baudRateOrNil stopBitsType:stopBitsTypeOrNil
   parityType:parityTypeOrNil dataBits:dataBitsOrNil
   inFlowCtrlType:inFlowCtrlTypeOrNil outFlowCtrlType:outFlowCtrlTypeOrNil
   xOnChar:xOnCharOrNil xOffChar:xOffCharOrNil

    "arguments are self describing; nil values mean: leave setting as is"

    ^ self new
	setPortName:portName
	baudRate:baudRateOrNil stopBitsType:stopBitsTypeOrNil
	parityType:parityTypeOrNil dataBits:dataBitsOrNil
	inFlowCtrlType:inFlowCtrlTypeOrNil outFlowCtrlType:outFlowCtrlTypeOrNil
	xOnChar:xOnCharOrNil xOffChar:xOffCharOrNil
! !

!SerialPort class methodsFor:'debugging'!

debug:aBoolean
    "turn on/off internal debugprints.
     This method is for ST/X debugging only and
     may  be removed in later versions"

%{  /* NOCONTEXT */

    __debugging__ = (aBoolean == true);
%}
    "
     SerialPort debug:true
     SerialPort debug:false
    "
! !

!SerialPort class methodsFor:'defaults'!

defaultPortName
    DefaultPortName notNil ifTrue:[^ DefaultPortName ].

    OperatingSystem isMSDOSlike ifTrue:[
	^ 'COM0'
    ].
    ^ '/dev/cua0'
! !

!SerialPort methodsFor:'accessing'!

setBaudRate:baudRateOrNil
    baudRate := baudRateOrNil.
!

setDataBits:dataBitsOrNil
    dataBits := dataBitsOrNil.
!

setParityType:parityTypeOrNil
    parityType := parityTypeOrNil.
!

setPortName:portNameArg
    (portNameArg startsWith:'C') ifTrue:[
	"/ add special port name to support COM ports > 9
	portName := '\\.\', portNameArg.
	^ self
    ].
    portName := portNameArg.
!

setPortName:portNameArg
	baudRate:baudRateOrNil stopBitsType:stopBitsTypeOrNil
	parityType:parityTypeOrNil dataBits:dataBitsOrNil
	inFlowCtrlType:inFlowCtrlTypeOrNil outFlowCtrlType:outFlowCtrlTypeOrNil
	xOnChar:xOnCharOrNil xOffChar:xOffCharOrNil

    self setPortName:portNameArg.
    baudRate := baudRateOrNil.
    stopBitsType := stopBitsTypeOrNil.
    parityType := parityTypeOrNil.
    dataBits := dataBitsOrNil.
    inFlowCtrlType := inFlowCtrlTypeOrNil.
    outFlowCtrlType := outFlowCtrlTypeOrNil.
    xOnChar := xOnCharOrNil.
    xOffChar := xOffCharOrNil.
!

setStopBitsType:stopBitsTypeOrNil
    stopBitsType := stopBitsTypeOrNil.
! !

!SerialPort methodsFor:'low level'!

baudRate:newRate
    handle isNil ifTrue:[
	"not open"
	baudRate := newRate.
	^ self
    ].
%{
    OBJ fp;

    fp = __INST(handle);
    if (__isSmallInteger(newRate)) {
	SERIALPORT port;
	int ret;

	port = PORT_FROM_FILE_OBJECT(fp);
#ifdef WIN32
	{
	    DCB dcb;

	    ZeroMemory(&dcb, sizeof(dcb));
	    dcb.DCBlength = sizeof(dcb);
	    GetCommState(port, &dcb);

	    dcb.BaudRate = __intVal(newRate);

	    if (! SetCommState(port, &dcb)) {
		RETURN(false);
	    }
	    __INST(baudRate) = newRate;
	    RETURN(true);
	}
# else /* ! WIN32 */
	/* add code for unix ioctl here ... */
# endif /* WIN32 */
    }
%}.
    self primitiveFailed.
!

closeFile
    "low level close"

%{
    OBJ t;

    t = __INST(handle);
    if (t != nil) {
	FILE *fp;

	__INST(handle) = nil;
	fp = __FILEVal(t);
#ifdef WIN32
	{
	    int ret;
	    SERIALPORT port;

	    port = PORT_FROM_FILE(fp);
# ifdef xxxDO_WRAP_CALLS
	    do {
		__threadErrno = 0;
		ret = STX_C_CALL1("fclose", fclose, fp);
	    } while ((ret < 0) && (__threadErrno == EINTR));

	    do {
		__threadErrno = 0;
		ret = STX_WSA_CALL1("CloseHandle", CloseHandle, port);
	    } while ((ret < 0) && (__threadErrno == EINTR));
# else
	    fclose(fp);
	    /* In WIN32, apparently the fclose() does not close the SerialPort hadle,
	     * so we have to do it here!
	     */
	    CloseHandle(port);
# endif /* DO_WRAP_CALLS */
	}
#else
	fclose(fp);
#endif
    }
%}

    "Modified: / 05-08-2011 / 14:13:35 / cg"
! !

!SerialPort methodsFor:'opening'!

open
    |errorSymbol errorNumber|

    handle notNil ifTrue:[
	^ self errorAlreadyOpen
    ].
%{
    FILE *fp;
    SERIALPORT port;
    char *__portName;
    int __setBaudRate = 1,
	__setDataBits = 1,
	__setXOnChar = 1,
	__setXOffChar = 1,
	__setInFlowCtrl = 1,
	__setOutFlowCtrl = 1,
	__setStopBits = 1,
	__setParityType = 1;
    int __baudRate, __dataBits;
    int __xOnChar, __xOffChar;
    int __inFlowCtrl, __outFlowCtrl;
    int __stopBits, __parityType;
#ifdef WIN32
    COMMTIMEOUTS timeouts;
    DCB dcb;
#endif /* WIN32 */

#   define FLOW_XONOFF    1
#   define FLOW_HARDWARE  2
#   define FLOW_NONE      3
#   define STOP_1         1
#   define STOP_2         2
#   define STOP_1_5       3
#   define PARITY_ODD     1
#   define PARITY_EVEN    2
#   define PARITY_NONE    3

    if (__isStringLike(__INST(portName))) {
	__portName = __stringVal(__INST(portName));
    } else {
	errorSymbol = @symbol(portName);
	goto getOutOfhere;
    }

    if (__isSmallInteger(__INST(baudRate))) {
	__baudRate = __intVal(__INST(baudRate));
    } else if (__INST(baudRate) == nil) {
	__setBaudRate = 0;
    } else {
	errorSymbol = @symbol(baudRate);
	goto getOutOfhere;
    }

    if (__isSmallInteger(__INST(dataBits))) {
	__dataBits = __intVal(__INST(dataBits));
    } else if (__INST(dataBits) == nil) {
	__setDataBits = 0;
    } else {
	errorSymbol = @symbol(dataBits);
	goto getOutOfhere;
    }

    if (__isSmallInteger(__INST(xOnChar))) {
	__xOnChar = __intVal(__INST(xOnChar));
    } else if (__isCharacter(__INST(xOnChar))) {
	__xOnChar = __intVal(_characterVal(__INST(xOnChar)));
    } else if (__INST(xOnChar) == nil) {
	__setXOnChar = 0;
    } else {
	errorSymbol = @symbol(xOnChar);
	goto getOutOfhere;
    }

    if (__isSmallInteger(__INST(xOffChar))) {
	__xOffChar = __intVal(__INST(xOffChar));
    } else if (__isCharacter(__INST(xOffChar))) {
	__xOffChar = __intVal(__characterVal(__INST(xOffChar)));
    } else if (__INST(xOffChar) == nil) {
	__setXOffChar = 0;
    } else {
	errorSymbol = @symbol(xOffChar);
	goto getOutOfhere;
    }

    if (__INST(inFlowCtrlType) == @symbol(xOnOff)) {
	__inFlowCtrl = FLOW_XONOFF;
    } else if (__INST(inFlowCtrlType) == @symbol(hardware)) {
	__inFlowCtrl = FLOW_HARDWARE;
    } else if (__INST(inFlowCtrlType) == @symbol(none)) {
	__inFlowCtrl = FLOW_NONE;
    } else if (__INST(inFlowCtrlType) == nil) {
	__setInFlowCtrl = 0;
    } else {
	errorSymbol = @symbol(inFlowCtrlType);
	goto getOutOfhere;
    }

    if (__INST(outFlowCtrlType) == @symbol(xOnOff)) {
	__outFlowCtrl = FLOW_XONOFF;
    } else if (__INST(outFlowCtrlType) == @symbol(hardware)) {
	__outFlowCtrl = FLOW_HARDWARE;
    } else if (__INST(outFlowCtrlType) == @symbol(none)) {
	__outFlowCtrl = FLOW_NONE;
    } else if (__INST(outFlowCtrlType) == nil) {
	__setOutFlowCtrl = 0;
    } else {
	errorSymbol = @symbol(outFlowCtrlType);
	goto getOutOfhere;
    }

    if (__INST(stopBitsType) == @symbol(stop1)) {
	__stopBits = STOP_1;
    } else if (__INST(stopBitsType) == @symbol(stop2)) {
	__stopBits = STOP_2;
    } else if (__INST(stopBitsType) == @symbol(stop1_5)) {
	__stopBits = STOP_1_5;
    } else if (__INST(stopBitsType) == nil) {
	__setStopBits = 0;
    } else {
	errorSymbol = @symbol(stopBitsType);
	goto getOutOfhere;
    }

    if (__INST(parityType) == @symbol(odd)) {
	__parityType = PARITY_ODD;
    } else if (__INST(parityType) == @symbol(even)) {
	__parityType = PARITY_EVEN;
    } else if (__INST(parityType) == @symbol(none)) {
	__parityType = PARITY_NONE;
    } else if (__INST(parityType) == nil) {
	__setParityType = 0;
    } else {
	errorSymbol = @symbol(parityType);
	goto getOutOfhere;
    }

#ifdef WIN32
    port = CreateFile(__portName,
	      GENERIC_READ | GENERIC_WRITE,
	      0,             /* comm devices must be opened with exclusive access */
	      NULL,          /* no security attrs */
	      OPEN_EXISTING, /* comm devices must use OPEN_EXISTING */
	      0,             /* no overlapped I/O */
	      NULL           /* hTemplate must be NULL for comm devices */
	   );

    if (port == INVALID_HANDLE_VALUE) {
	console_fprintf(stderr, "Win32OS [info]: serial port open failed\n");
	errorNumber = __mkSmallInteger( __WIN32_ERR(GetLastError()) );
	errorSymbol = @symbol(openFailed);
	goto getOutOfhere;
    }

    /* Flush the driver */
    PurgeComm( port, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR );

    /* Set driver buffer sizes */
    SetupComm( port, 4096 /*SERIAL_IN_QUEUE_SIZE*/, 4096 /*SERIAL_OUT_QUEUE_SIZE*/);

    /* Reset timeout constants */
    timeouts.ReadIntervalTimeout= 0xFFFFFFFF;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.ReadTotalTimeoutConstant = 0;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 0;
    SetCommTimeouts( port, &timeouts );

    /* Set communication parameters */
    ZeroMemory(&dcb, sizeof(dcb));
    dcb.DCBlength = sizeof(dcb);
    GetCommState(port, &dcb);

    if (__setBaudRate) dcb.BaudRate = __baudRate;
    if (__setDataBits) dcb.ByteSize = __dataBits;
    if (__setXOnChar)  dcb.XonChar = __xOnChar;
    if (__setXOffChar) dcb.XoffChar = __xOffChar;

    if (__setStopBits) {
	/* set stop bits */
	switch(__stopBits) {
	    case STOP_1_5: dcb.StopBits = 1; break; /* 1.5 stop bits */
	    case STOP_1: dcb.StopBits = 0; break; /* 1 stop bit */
	    case STOP_2: dcb.StopBits = 2; break; /* 2 stop bits */
	    default:
		errorSymbol = @symbol(stopBits);
		goto errExit;
	}
    }

    if (__setParityType) {
	/* set parity */
	switch(__parityType) {
	    case PARITY_NONE: dcb.Parity = NOPARITY; break;
	    case PARITY_ODD: dcb.Parity = ODDPARITY; break;
	    case PARITY_EVEN: dcb.Parity = EVENPARITY; break;
	    default:
		errorSymbol = @symbol(parityType);
		goto errExit;
	}
    }

    if (__setInFlowCtrl) {
	/* set control flow */
	dcb.fInX = FALSE;
	dcb.fDtrControl = FALSE;
	if (__inFlowCtrl == FLOW_XONOFF) dcb.fInX = TRUE;  /* XOn/XOff handshaking */
	if (__inFlowCtrl == FLOW_HARDWARE) dcb.fDtrControl = TRUE;  /* hardware handshaking */
    }
    if (__setOutFlowCtrl) {
	dcb.fOutX = FALSE;
	dcb.fOutxCtsFlow = FALSE;

	if (__outFlowCtrl == FLOW_XONOFF) dcb.fOutX = TRUE;  /* XOn/XOff handshaking */
	if (__outFlowCtrl == FLOW_HARDWARE) dcb.fOutxCtsFlow = TRUE;  /* hardware handshaking */
    }

    if (! SetCommState(port, &dcb)) {
	console_fprintf(stderr, "Win32OS [info]: serial port comm-setup failed\n");
	errorNumber = __mkSmallInteger( __WIN32_ERR(GetLastError()) );
	goto errExit;
    }
# endif /* WIN32 */

    /*
     * make it a FILE *
     */
# ifdef WIN32
    {
	int _fd;
	__stxWrapApiEnterCritical();
	_fd = _open_osfhandle((long)port, 0);
	fp = fdopen(_fd, "r+");
	__stxWrapApiLeaveCritical();
    }
# else
    fp = fdopen(port, "r+");
# endif
    if (! fp) {
	console_fprintf(stderr, "Win32OS [info]: fdopen failed\n");
	errorNumber = __MKSMALLINT(errno);
    errExit: ;
# ifdef WIN32
	CloseHandle(port);
# endif
	goto getOutOfhere;
    }

    {
	OBJ t;

	t = __MKEXTERNALADDRESS(fp);
	__INST(handle) = t;
	__STORE(self, t);
    }

getOutOfhere: ;
#   undef FLOW_XONOFF
#   undef FLOW_HARDWARE
#   undef FLOW_NONE
#   undef STOP_1
#   undef STOP_2
#   undef STOP_1_5
#   undef PARITY_ODD
#   undef PARITY_EVEN
#   undef PARITY_NONE

%}.
    "all ok?"
    handle notNil ifTrue:[
	Lobby register:self.
    ] ifFalse:[
	errorNumber isNil ifTrue:[
	    self error:'invalid argument(s)'.
	] ifFalse:[
	    ((OperatingSystem errorHolderForNumber:errorNumber) parameter:portName) reportError
	].
    ].

    "Modified: / 05-08-2011 / 14:14:08 / cg"
! !

!SerialPort methodsFor:'printing & storing'!

printOn:aStream
    aStream nextPutAll:'SerialPort('.
    portName printOn:aStream.
    aStream nextPut:$/.
    baudRate printOn:aStream.
    aStream nextPut:$).
! !

!SerialPort methodsFor:'queries'!

getName
    "return the name; here, we return the devices name"

    ^ portName
! !

!SerialPort class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libbasic2/SerialPort.st,v 1.20 2012-09-03 20:23:42 cg Exp $'
!

version_CVS
    ^ '$Header: /cvs/stx/stx/libbasic2/SerialPort.st,v 1.20 2012-09-03 20:23:42 cg Exp $'
! !