RS232 / USB read/write?

Mike James foo at bar.com
Mon Feb 1 05:19:50 PST 2010


Brian Hay Wrote:

> I'm a bit green with D. I've done some basic D1 + Tango + Derelict stuff 
> but am keen to learn more about D2 + Phobos.
> 
> As a learning project I thought I'd like to try reading/writing data 
> streams from/to RS232 and/or USB devices, something I've never done 
> before in any language.
> 
> Can anyone give me some pointers and online references? Are there any D 
> libraries for this?

This something I wrote a while ago for D1 + Tango, it will probably need a bit of tweeking to get it to the latest compiler version. Sorry for having to include it in the body text...

==============================================
module comms;

private import  tango.sys.Common,
                tango.time.Time,
                tango.stdc.stdint,
                tango.io.Stdout,
                StdC    = tango.stdc.String,
                Integer = tango.text.convert.Integer,
                Float   = tango.text.convert.Float,
                tango.text.Util,
                tango.stdc.Stringz,
                tango.core.Thread,
                tango.io.device.ThreadConduit;
private import  ascii;


public class Comms {
    /*
     *  Constants
     */
    private const uint      RX_CHAR_COUNT           = 1;
    private const char[]    ALT_NAME_PREFIX         = r"\\.\";
    private const char[]    PORT_NOT_OPEN_EXCEPTION = "Serial Port not Open";
    public  const uint      INFINITE_TIMEOUT        = uint.max;

    /*
     *  Enumerations
     */
    public enum Parity : ubyte {
        None    = cast(ubyte)PARITY_NONE,
        Odd     = cast(ubyte)PARITY_ODD,
        Even    = cast(ubyte)PARITY_EVEN,
        Mark    = cast(ubyte)PARITY_MARK,
        Space   = cast(ubyte)PARITY_SPACE
    }

    public enum StopBits : ubyte {
        One             = cast(ubyte)ONESTOPBIT,
        OnePointFive    = cast(ubyte)ONE5STOPBITS,
        Two             = cast(ubyte)TWOSTOPBITS
    }

    public enum Handshake : int {
        None,
        Hardware,
        Software
    }

    private enum SigState : int {
        Low,
        High,
        NotUsed
    }

    private enum WaitFor {
        ThreadTerminate,
        Event,
        MaxSize
    }

    private HANDLE  hComm               = null;
    private HANDLE  hRxThreadStarted    = null;
    private HANDLE  hRxThreadDone       = null;
    private HANDLE  hReadLine           = null;

    private Thread          receiveThread;
    private ThreadConduit   tcRxConduit;

    private bool        isOpen_         = false;
    private char[]      portName_       = "COM1";
    private uint        baudrate_       = 9600;
    private int         dataBits_       = 8;
    private StopBits    stopBits_       = StopBits.One;
    private Parity      parity_         = Parity.None;
    private Handshake   handshake_      = Handshake.None;
    private char        xonChar_        = ASCII.DC1;
    private char        xoffChar_       = ASCII.DC3;
    private char        errorChar_      = ASCII.NUL;
    private uint        rxQueue_        = 0;
    private uint        txQueue_        = 0;
    private SigState    rtsState_       = SigState.NotUsed;
    private SigState    dtrState_       = SigState.NotUsed;
    private SigState    breakState_     = SigState.NotUsed;
    private uint        rxTimeout_      = INFINITE_TIMEOUT;
    private uint        txTimeoutConst_ = 0;
    private uint        txTimeoutMult_  = 0;
    private short       xonLowLevel_    = 0;
    private short       xoffHighLevel_  = 0;
    private bool        discardNull_    = false;

    /*
     *  Delegates
     */
    private void delegate(uint, char[]) dgDataEvent;
    private void delegate() dgTxEmptyEvent;
    private void delegate() dgBreakEvent;
    private void delegate(ModemStatus, ModemStatus) dgStatusChangeEvent;
    private void delegate(ErrorStatus) dgErrorEvent;

    this() {
        commonInit();
    }

    this(char[] portName, uint baudrate, int dataBits, StopBits stopBits, Parity parity) {
        portName_   = portName;
        baudrate_   = baudrate;
        dataBits_   = dataBits;
        stopBits_   = stopBits;
        parity_     = parity;

        commonInit();
    }

    this(char[] portName, uint baudrate) {
        portName_   = portName;
        baudrate_   = baudrate;

        commonInit();
    }

    ~this() {
        close();
    }

    private void commonInit() {
        tcRxConduit = new ThreadConduit();
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current state of the serial port.
     */
    public bool isOpen() {
        return isOpen_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200, 128000, 256000, default 9600.
     *  Returns:
     *      Current Baudrate setting.
     */
    public uint baudrate(uint value) {
        return baudrate_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Baudrate.
     */
    public uint baudrate() {
        return baudrate_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = 5, 6, 7 or 8, default 8.
     *  Returns:
     *      Current data bits setting.
     */
    public int dataBits(int value) {
        return dataBits_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current data bits setting.
     */
    public int dataBits() {
        return dataBits_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = One, OnePointFive or Two, default One.
     *  Returns:
     *      Current stop bits setting.
     */
    public StopBits stopBits(StopBits value) {
        return stopBits_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current stop bits setting.
     */
    public StopBits stopBits() {
        return stopBits_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = None, Odd, Even, Mark or Space, default None.
     *  Returns:
     *      Current parity setting.
     */
    public Parity parity(Parity value) {
        return parity_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current parity setting.
     */
    public Parity parity() {
        return parity_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = port name i.e. COM1, default COM1.
     *  Returns:
     *      Current port name setting.
     */
    public char[] portName(char[] value) {
        return portName_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current port name setting.
     */
    public char[] portName() {
        return portName_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = None, Hardware or Software, default None.
     *  Returns:
     *      Current handshake setting.
     */
    public Handshake handshake(Handshake value) {
        return handshake_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current handshake setting.
     */
    public Handshake handshake() {
        return handshake_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = XON char.
     *  Returns:
     *      Current XON char.
     */
    public char xonChar(char value) {
        return xonChar_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current XON char.
     */
    public char xonChar() {
        return xonChar_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = XOFF char.
     *  Returns:
     *      Current XOFF char.
     */
    public char xoffChar(char value) {
        return xoffChar_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current XOFF char.
     */
    public char xoffChar() {
        return xoffChar_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = size of RX queue.
     *  Returns:
     *      Current size of RX queue.
     */
    public uint rxQueue(uint value) {
        return rxQueue_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current size of RX queue.
     */
    public uint rxQueue() {
        return rxQueue_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = size of TX queue.
     *  Returns:
     *      Current size of TX queue.
     */
    public uint txQueue(uint value) {
        return txQueue_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current size of TX queue.
     */
    public uint txQueue() {
        return txQueue_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = required RTS state.
     *  Returns:
     *      Current RTS state.
     */
    public bool rts(bool value) {
        if (rtsState_ > SigState.High) {
            return false;
        }

        if (value) {
            if (EscapeCommFunction(hComm, SETRTS)) {
                rtsState_ = SigState.High;
            } else {
                throw new CommException("EscapeCommFunction [RTS]");
            }
        } else {
            if (EscapeCommFunction(hComm, CLRRTS)) {
                rtsState_ = SigState.Low;
            } else {
                throw new CommException("EscapeCommFunction [RTS]");
            }
        }
        return cast(bool)(rtsState_ == SigState.High);
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current RTS state.
     */
    public bool rts() {
        return cast(bool)(rtsState_ == SigState.High);
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = required DTR state.
     *  Returns:
     *      Current DTR state.
     */
    public bool dtr(bool value) {
        if (dtrState_ > SigState.High) {
            return false;
        }

        if (value) {
            if (EscapeCommFunction(hComm, SETDTR)) {
                dtrState_ = SigState.High;
            } else {
                throw new CommException("EscapeCommFunction [DTR]");
            }
        } else {
            if (EscapeCommFunction(hComm, CLRDTR)) {
                dtrState_ = SigState.Low;
            } else {
                throw new CommException("EscapeCommFunction [DTR]");
            }
        }
        return cast(bool)(dtrState_ == SigState.High);
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current DTR state.
     */
    public bool dtr() {
        return cast(bool)(dtrState_ == SigState.High);
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = required BREAK state.
     *  Returns:
     *      Current BREAK state.
     */
    public bool brk(bool value) {
        if (breakState_ > SigState.High) {
            return false;
        }

        if (value) {
            if (EscapeCommFunction(hComm, SETBREAK)) {
                breakState_ = SigState.High;
            } else {
                throw new CommException("EscapeCommFunction [BREAK]");
            }
        } else {
            if (EscapeCommFunction(hComm, CLRBREAK)) {
                breakState_ = SigState.Low;
            } else {
                throw new CommException("EscapeCommFunction [BREAK]");
            }
        }
        return cast(bool)(breakState_ == SigState.High);
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current BREAK state.
     */
    public bool brk() {
        return cast(bool)(breakState_ == SigState.High);
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = size of readLine timeout in ms.
     *  Returns:
     *      Current size of readLine timeout in ms.
     */
    public uint rxTimeout(uint value) {
        return rxTimeout_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current size of readLine timeout in ms.
     */
    public uint rxTimeout() {
        return rxTimeout_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = size of WriteTotalTimeoutMultiplier in ms.
     *  Returns:
     *      Current size of WriteTotalTimeoutMultiplier in ms.
     */
    public uint txTimeoutMult(uint value) {
        return txTimeoutMult_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current size of WriteTotalTimeoutMultiplier in ms.
     */
    public uint txTimeoutMult() {
        return txTimeoutMult_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = size of WriteTotalTimeoutConstant in ms.
     *  Returns:
     *      Current size of WriteTotalTimeoutConstant in ms.
     */
    public uint txTimeoutConst(uint value) {
        return txTimeoutConst_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current size of WriteTotalTimeoutConstant in ms.
     */
    public uint txTimeoutConst() {
        return txTimeoutConst_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = minimum number of bytes in input buffer before XON char is sent.
     *  Returns:
     *      Current size of XON low level.
     */
    public short xonLowLevel(short value) {
        return xonLowLevel_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current size of XON low level.
     */
    public short xonLowLevel() {
        return xonLowLevel_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = maximum number of bytes in input buffer before XOFF char is sent.
     *  Returns:
     *      Current size of XOFF high level.
     */
    public short xoffHighLevel(short value) {
        return xoffHighLevel_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current size of XOFF high level.
     */
    public short xoffHighLevel() {
        return xoffHighLevel_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = true or false, default false.
     *  Returns:
     *      Current discard null setting.
     */
    public bool discardNull(bool value) {
        return discardNull_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current discard null setting.
     */
    public bool discardNull() {
        return discardNull_;
    }

    /*************************************************************
     *  Write property
     *  Params:
     *      value = required error char, default NULL.
     *  Returns:
     *      Current error char.
     */
    public char errorChar(char value) {
        return errorChar_ = value;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      Current error char.
     */
    public char errorChar() {
        return errorChar_;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      number of bytes in receive buffer.
     */
    public uint bytesToRead() {
        return tcRxConduit.remaining;
    }

    /*************************************************************
     *  Read property
     *  Returns:
     *      modem status.
     */
    public ModemStatus modemStatus() {
        if (isOpen_) {
            uint scStatus = 0;

            if (!GetCommModemStatus(hComm, &scStatus)) {
                throw new CommException("GetCommModemStatus");
            }
            return new ModemStatus(scStatus);
        } else {
            throw new CommException(PORT_NOT_OPEN_EXCEPTION, false);
        }
    }

    /*************************************************************
     *  Method: opens the serial port.
     *  Returns:
     *      none.
     */
    public void open() {
        if (hComm !is null) {
            throw new CommException("Port " ~ portName_ ~ " already open");
        }

        if ((hComm = CreateFileA(toStringz(portName_),
                                 GENERIC_READ | GENERIC_WRITE,
                                 0,
                                 null,
                                 OPEN_EXISTING,
                                 FILE_FLAG_OVERLAPPED,
                                 null)) == INVALID_HANDLE_VALUE) {
            // try with alternate naming
            if ((hComm = CreateFileA(toStringz(ALT_NAME_PREFIX ~ portName_),
                                     GENERIC_READ | GENERIC_WRITE,
                                     0,
                                     null,
                                     OPEN_EXISTING,
                                     FILE_FLAG_OVERLAPPED,
                                     null)) == INVALID_HANDLE_VALUE) {
                throw new CommException("Failed to open " ~ portName_);
            }
        }

        if (GetFileType(hComm) != FILE_TYPE_CHAR) {
            throw new CommException(portName_ ~ " file handle is not valid");
        }

        DCB dcb = {0};

        dcb.DCBlength = dcb.sizeof;

        if (GetCommState(hComm, &dcb) == 0) {
            throw new CommException("GetCommState");
        }

        dcb.BaudRate = baudrate_;
        dcb.ByteSize = dataBits_;
        dcb.Parity   = parity_;
        dcb.StopBits = stopBits_;
        dcb.XonChar  = xonChar_;
        dcb.XoffChar = xoffChar_;
        // flag settings
        dcb.flag0    = bm_DCB_fBinary;   // must always be set to 1

        // handshake flags
        switch (handshake_) {
            case Handshake.None:
                // dcb.fOutxCtsFlow = dcb.fOutxDsrFlow = dcb.fOutX = dcb.fInX = false;
                dcb.flag0   |= (RTS_CONTROL_DISABLE << bp_DCB_fRtsControl);
                dcb.flag0   |= (DTR_CONTROL_DISABLE << bp_DCB_fDtrControl);
                break;

            case Handshake.Hardware:
                dcb.flag0   |= (1 << bp_DCB_fOutxCtsFlow);
                dcb.flag0   |= (1 << bp_DCB_fOutxDsrFlow);
                // dcb.fOutX = dcb.fInX = false;
                dcb.flag0   |= (RTS_CONTROL_HANDSHAKE << bp_DCB_fRtsControl);
                dcb.flag0   |= (DTR_CONTROL_HANDSHAKE << bp_DCB_fDtrControl);
                rtsState_   = SigState.High;
                dtrState_   = SigState.High;
                break;

            case Handshake.Software:
                // dcb.fOutxCtsFlow = dcb.fOutxDsrFlow = false;
                dcb.flag0   |= (1 << bp_DCB_fOutX);
                dcb.flag0   |= (1 << bp_DCB_fInX);
                dcb.flag0   |= (RTS_CONTROL_DISABLE << bp_DCB_fRtsControl);
                dcb.flag0   |= (DTR_CONTROL_DISABLE << bp_DCB_fDtrControl);
                break;

            default:
                throw new CommException("Handshake");
                break;
        }

        if ((xonLowLevel == 0) || (xoffHighLevel == 0)) {
            COMMPROP cp;

            if (!GetCommProperties(hComm, &cp)) {
                throw new CommException("GetCommProperties");
            }

            const int   XON_XOFF_LIMITS_PERCENT = 10;
            const short XON_XOFF_DEFAULT_LIMITS = 8;

            if (cp.dwCurrentRxQueue > 0) {
                dcb.XoffLim = dcb.XonLim = cast(short)(cp.dwCurrentRxQueue / XON_XOFF_LIMITS_PERCENT);
            } else {
                dcb.XoffLim = dcb.XonLim = XON_XOFF_DEFAULT_LIMITS;
            }
        } else {

            dcb.XoffLim = xoffHighLevel;
            dcb.XonLim  = xonLowLevel;
        }

        if (discardNull_) {
            dcb.flag0 |= (1 << bp_DCB_fNull);
        }

        // set the parity flag if parity-checking set
        if ((parity_ == Parity.Odd) ||
            (parity_ == Parity.Even)) {
            dcb.flag0 |= (1 << bp_DCB_fParity);
        }

        dcb.ErrorChar = errorChar_;

        if (SetCommState(hComm, &dcb) == 0) {
            throw new CommException("SetCommState");
        }

        if ((rxQueue != 0) || (txQueue != 0)) {
            if (!SetupComm(hComm, rxQueue, txQueue)) {
                throw new CommException("SetupComm");
            }
        }

        COMMTIMEOUTS commTimeouts;

        commTimeouts.ReadIntervalTimeout         = INFINITE_TIMEOUT;
        commTimeouts.ReadTotalTimeoutMultiplier  = 0;
        commTimeouts.ReadTotalTimeoutConstant    = 0;
        commTimeouts.WriteTotalTimeoutMultiplier = txTimeoutMult_;
        commTimeouts.WriteTotalTimeoutConstant   = txTimeoutConst_;

        if (SetCommTimeouts(hComm, &commTimeouts) == 0) {
            throw new CommException("SetCommTimeouts");
        }

        hRxThreadStarted    = CreateEventA(null, false, false, null);
        hRxThreadDone       = CreateEventA(null, false, false, null);
        hReadLine           = CreateEventA(null, true,  false, null);

        receiveThread       = new Thread(&receiveHandler);
        receiveThread.start();

        uint waitForThreadStart = WaitForSingleObject(hRxThreadStarted, INFINITE);
        CloseHandle(hRxThreadStarted);
        hRxThreadStarted = INVALID_HANDLE_VALUE;

        isOpen_ = true;
    }

    /*************************************************************
     *  Method: closes the serial port.
     *  Returns:
     *      none.
     */
    public void close() {
        if (isOpen_) {
            flush();

            tcRxConduit.close();
            SetEvent(hRxThreadDone);
            receiveThread.join();

            CloseHandle(hComm);
            hComm = INVALID_HANDLE_VALUE;
        }
    }

    /*************************************************************
     *  Delegate: called when data is available.
     *  Returns:
     *      none.
     */
    public void onData(void delegate(uint, char[]) dg) {
        dgDataEvent = dg;
    }

    /*************************************************************
     *  Delegate: called when transmit buffer is empty.
     *  Returns:
     *      none.
     */
    public void onTxEmpty(void delegate() dg) {
        dgTxEmptyEvent = dg;
    }

    /*************************************************************
     *  Delegate: called when a break is detected.
     *  Returns:
     *      none.
     */
    public void onBreak(void delegate() dg) {
        dgBreakEvent = dg;
    }

    /*************************************************************
     *  Delegate: called when line status change is detected.
     *  Returns:
     *      none.
     */
    public void onStatusChange(void delegate(ModemStatus, ModemStatus) dg) {
        dgStatusChangeEvent = dg;
    }

    /*************************************************************
     *  Delegate: called when error event is detected.
     *  Returns:
     *      none.
     */
    public void onError(void delegate(ErrorStatus) dg) {
        dgErrorEvent = dg;
    }

    /*************************************************************
     *  Method: gets a char from the receive buffer.
     *  Returns:
     *      int - char if available or -1 if buffer empty.
     */
    public int readChar() {
        char[1] ch;

        if (tcRxConduit.remaining > 0) {
            tcRxConduit.input.read(ch);
        } else {
            // return -1 if end of stream
            return -1;
        }

        return cast(int)ch[0];
    }

    /*************************************************************
     *  Method: gets a byte from the receive buffer.
     *  Returns:
     *      int - byte if available or -1 if buffer empty.
     */
    public int readByte() {
        return readChar();
    }

    /*************************************************************
     *  Method: gets all chars from the receive buffer.
     *  Returns:
     *      char string if available or empty string if not.
     */
    public char[] readExisting() {
        if (tcRxConduit.remaining > 0) {
            char[] str = new char[tcRxConduit.remaining];

            tcRxConduit.input.read(str);

            return str;
        } else {
            return "";
        }
    }

    /*************************************************************
     *  Method: gets a string terminated with a newline from the receive buffer.
     *  Returns:
     *      int - char string if available or CommTimeoutException if timedout.
     */
    public char[] readLine() {
        char[] str;
        char[1] ch;
        uint n = 0;

        str.length = 256;

        for (;;) {
            switch (WaitForSingleObject(hReadLine, rxTimeout_)) {
                ResetEvent(hReadLine);
                case WAIT_OBJECT_0:
                    tcRxConduit.input.read(ch);
                    switch (ch[0]) {
                        case '\r':
                            str.length = n;
                            return str;
                            break;

                        case '\n':
                            break;

                        default:
                            if (n == str.length) {
                                str.length = str.length + 64;
                            }
                            str[n++] = ch[0];
                            break;
                    }
                    break;

                case WAIT_TIMEOUT:
                    throw new CommTimeoutException("Comms Timeout");
                    break;

                case WAIT_FAILED:
                    throw new CommException("Comms ReadLine Timeout");
                    break;
            }
        }
    }

    /*************************************************************
     *  Method: transmits a char ch immediately - ignores the transmit buffer.
     *  Returns:
     *      none.
     */
    public void writeImmediate(char ch) {
        if (isOpen_) {
            if (!TransmitCommChar(hComm, ch)) {
                throw new CommException("TransmitCommChar");
            }
        } else {
            throw new CommException(PORT_NOT_OPEN_EXCEPTION, false);
        }
    }

    /*************************************************************
     *  Method: transmits the string str.
     *  Returns:
     *      none.
     */
    public void write(char[] str) {
        writeStr(str);
    }

    /*************************************************************
     *  Method: transmits the string str appended with a CR/LF.
     *  Returns:
     *      none.
     */
    public void writeln(char[] str) {
        writeStr(str ~ "\r\n");
    }

    /*************************************************************
     *  Method: transmits the char ch.
     *  Returns:
     *      none.
     */
    public void write(char ch) {
        char[1] str;
        str[0] = ch;

        writeStr(str);
    }

    private void writeStr(char[] str) {
        if (isOpen_) {
            uint numBytesWritten = 0;
            OVERLAPPED ovTransmit = {0};
            ovTransmit.hEvent = CreateEventA(null, true, false, null);

            if (!WriteFile(hComm, cast(void*)str, str.length, &numBytesWritten, &ovTransmit)) {
                if (SysError.lastCode() == ERROR_IO_PENDING) {
                    switch(WaitForSingleObject(ovTransmit.hEvent, INFINITE_TIMEOUT)) {
                        case WAIT_OBJECT_0:
                            if (GetOverlappedResult(hComm, &ovTransmit, &numBytesWritten, false) == 0) {
                                throw new CommException("GetOverlappedResult");
                            } else {
                                if (str.length != numBytesWritten) {
                                    throw new CommException("Write Failed", false);
                                }
                            }
                            break;

                        case WAIT_FAILED:
                            throw new CommException("WaitforSingleObject");
                            break;
                    }
                } else {
                    throw new CommException("WriteFile");
                }
            }
            CloseHandle(ovTransmit.hEvent);
        } else {
            throw new CommException(PORT_NOT_OPEN_EXCEPTION, false);
        }
    }

    /*************************************************************
     *  Method: flushes the transmit buffer.
     *  Returns:
     *      none.
     */
    public void flush() {
        if (isOpen_) {
            if (FlushFileBuffers(hComm) == 0) {
                throw new CommException("FlushFileBuffers");
            }
        } else {
            throw new CommException(PORT_NOT_OPEN_EXCEPTION, false);
        }
    }

    private void receiveHandler() {
        bool    receiveThreadActive = true;
        bool    firstTime = true;
        uint    evMask = 0;
        uint    nBytesRead;
        byte[]  localRxBuffer = new byte[RX_CHAR_COUNT];
        HANDLE[WaitFor.MaxSize] hWaitfor;

        OVERLAPPED ovReceive;
        StdC.memset(&ovReceive, 0x00, ovReceive.sizeof);
        ovReceive.hEvent = CreateEventA(null, true, false, null);

        hWaitfor[WaitFor.ThreadTerminate] = hRxThreadDone;

        try {
            while (receiveThreadActive) {
                if (!SetCommMask(hComm, EV_RXCHAR | EV_TXEMPTY | EV_ERR | EV_BREAK | EV_CTS | EV_DSR | EV_RLSD | EV_RING)) {
                    throw new CommException("SetCommMask");
                }

                if (firstTime) {
                    SetEvent(hRxThreadStarted);
                    firstTime = false;
                }

                evMask = 0;
                if (!WaitCommEvent(hComm, &evMask, &ovReceive)) {
                    if (SysError.lastCode() != ERROR_IO_PENDING) {
                        throw new CommException("WaitCommEvent");
                    }
                }

                hWaitfor[WaitFor.Event] = ovReceive.hEvent;

                switch (WaitForMultipleObjects(WaitFor.MaxSize, cast(HANDLE*)hWaitfor, false, INFINITE)) {
                    case WAIT_OBJECT_0 + WaitFor.ThreadTerminate:
                        receiveThreadActive = false;
                        isOpen_             = false;
                        break;

                    case WAIT_OBJECT_0 + WaitFor.Event:
                        if ((evMask & EV_ERR) == EV_ERR) {
                            uint errors = 0;

                            if (ClearCommError(hComm, &errors, null)) {
                                bool isError = false;

                                if (((errors & CE_FRAME)     == CE_FRAME)    ||
                                    ((errors & CE_IOE)       == CE_IOE)      ||
                                    ((errors & CE_OVERRUN)   == CE_OVERRUN)  ||
                                    ((errors & CE_RXOVER)    == CE_RXOVER)   ||
                                    ((errors & CE_RXPARITY)  == CE_RXPARITY) ||
                                    ((errors & CE_TXFULL)    == CE_TXFULL)) {
                                    isError = true;
                                }

                                if (isError) {
                                    if (dgErrorEvent !is null) {
                                        dgErrorEvent(new ErrorStatus(errors));
                                    }
                                } else {
                                    if (errors == CE_BREAK) {
                                        evMask |= EV_BREAK;
                                    } else {
                                        throw new CommException("Break Error", false);
                                    }
                                }
                            } else {
                                throw new CommException("ClearCommError");
                            }
                        }

                        if ((evMask & EV_RXCHAR) == EV_RXCHAR) {
                            do {
                                if (!ReadFile(hComm, cast(void*)localRxBuffer, RX_CHAR_COUNT, &nBytesRead, &ovReceive)) {
                                    throw new CommException("ReadFile");
                                }
                                if (nBytesRead == RX_CHAR_COUNT) {
                                    tcRxConduit.output.write(localRxBuffer);
                                }
                            } while(nBytesRead > 0);

                            if (dgDataEvent !is null) {
                                uint len    = tcRxConduit.remaining;
                                char[] str  = new char[len];

                                tcRxConduit.input.read(str);

                                dgDataEvent(len, str);
                            }

                            SetEvent(hReadLine);
                        }

                        if ((evMask & EV_TXEMPTY) == EV_TXEMPTY) {
                            if (dgTxEmptyEvent !is null) {
                                dgTxEmptyEvent();
                            }
                        }

                        if ((evMask & EV_BREAK) == EV_BREAK) {
                            if (dgBreakEvent !is null) {
                                dgBreakEvent();
                            }
                        }

                        if (dgStatusChangeEvent !is null) {
                            uint scMask = 0;

                            if ((evMask & EV_CTS)   == EV_CTS)  { scMask |= MS_CTS_ON;  }
                            if ((evMask & EV_DSR)   == EV_DSR)  { scMask |= MS_DSR_ON;  }
                            if ((evMask & EV_RLSD)  == EV_RLSD) { scMask |= MS_RLSD_ON; }
                            if ((evMask & EV_RING)  == EV_RING) { scMask |= MS_RING_ON; }

                            if (scMask != 0) {
                                uint scStatus = 0;

                                if (!GetCommModemStatus(hComm, &scStatus)) {
                                    throw new CommException("GetCommModemStatus");
                                }
                                dgStatusChangeEvent(new ModemStatus(scMask), new ModemStatus(scStatus));
                            }
                        }
                        break;

                    case WAIT_FAILED:
                        throw new CommException("WaitForMultipleObjects");
                        break;
                }

                ResetEvent(ovReceive.hEvent);
            }
        }

        catch (CommException e) {
            Stdout.format("Comms Exception: {0}", e.toString);
        }
    }

    /*
     *  Exceptions
     */
    private class CommException : Exception {
        this(char[] msg, bool showSysErr = true) {
            if (showSysErr) {
                msg ~= " - " ~ SysError.lastMsg();
            }
            super(msg);
        }
    }

    private class CommTimeoutException : Exception {
        this(char[] msg) {
            super(msg);
        }
    }
}


public class ModemStatus {
    private uint status;

    this(uint s) {
        status = s;
    }

    public bool cts() {
        return cast(bool)((status & MS_CTS_ON) == MS_CTS_ON);
    }

    public bool dsr() {
        return cast(bool)((status & MS_DSR_ON) == MS_DSR_ON);
    }

    public bool rlsd() {
        return cast(bool)((status & MS_RLSD_ON) == MS_RLSD_ON);
    }

    public bool ri() {
        return cast(bool)((status & MS_RING_ON) == MS_RING_ON);
    }
}


public class ErrorStatus {
    private uint error;

    this(uint err) {
        error = err;
    }

    public bool frame() {
        return cast(bool)((error & CE_FRAME) == CE_FRAME);
    }

    public bool io() {
        return cast(bool)((error & CE_IOE) == CE_IOE);
    }

    public bool overrun() {
        return cast(bool)((error & CE_OVERRUN) == CE_OVERRUN);
    }

    public bool rxOverflow() {
        return cast(bool)((error & CE_RXOVER) == CE_RXOVER);
    }

    public bool rxParity() {
        return cast(bool)((error & CE_RXPARITY) == CE_RXPARITY);
    }

    public bool txFull() {
        return cast(bool)((error & CE_TXFULL) == CE_TXFULL);
    }
}


debug (COMMS) {
private import  tango.io.Console,
                tango.io.stream.TextFileStream;

    int main() {
        Comms comms = new Comms("COM1", 38400, 8, Comms.StopBits.One, Comms.Parity.None);

        void textIn(uint count, char[] str) {
            /*
            char ch;
            while (comms.bytesToRead > 0) {
                switch (ch = cast(char)comms.readChar()) {
                    case '\r':
                        Stdout.newline;
                        break;

                    default:
                        Stdout.format("{0}", ch).flush;
                        break;
                }
            }
            */
            for (uint n = 0; n < count; ++n) {
                 switch (str[n]) {
                    case '\r':
                        Stdout.newline;
                        break;

                    default:
                        Stdout.format("{0}", str[n]).flush;
                        break;
                }
            }
        }

        void txEmptyDetected() {
            Stdout.formatln("TX EMPTY");
        }

        void breakDetected() {
            Stdout.formatln("BREAK");
        }

        void statusChange(ModemStatus mask, ModemStatus status) {
            Stdout.formatln("CTS  : {0}, {1}", mask.cts,  status.cts);
            Stdout.formatln("DSR  : {0}, {1}", mask.dsr,  status.dsr);
            Stdout.formatln("RLSD : {0}, {1}", mask.rlsd, status.rlsd);
            Stdout.formatln("RI   : {0}, {1}", mask.ri,   status.ri);
            Stdout.newline;
        }

        void errorsDetected(ErrorStatus es) {
            Stdout.formatln("Frame       : {0}", es.frame);
            Stdout.formatln("IO          : {0}", es.io);
            Stdout.formatln("Overrun     : {0}", es.overrun);
            Stdout.formatln("RX Overflow : {0}", es.rxOverflow);
            Stdout.formatln("RX Parity   : {0}", es.rxParity);
            Stdout.formatln("TX Full     : {0}", es.txFull);
            Stdout.newline;
        }

        comms.handshake = Comms.Handshake.None;

        comms.onData            = &textIn;
        //comms.onTxEmpty         = &txEmptyDetected;
        comms.onBreak           = &breakDetected;
        comms.onStatusChange    = &statusChange;
        comms.onError           = &errorsDetected;
        comms.rxTimeout = 1000;
        comms.open();

        Stdout.formatln("Modem Status : {0}", comms.modemStatus.cts);

        char[] str = new char[10];

        comms.writeln("Hello World");
        comms.writeln("the quick brown fox jumped over the lazy dog");
        comms.writeImmediate('!');

        //auto input = new TextFileInput("comms.d");
        //foreach (line; input) {
        //    comms.writeln(line);
        //}
        //input.close;

        /*
        while (1) {
            if (comms.bytesToRead > 0) {
                try {
                    Stdout.formatln("{0}", comms.readLine());
                }

                catch (Comms.CommTimeoutException e) {
                    Stdout.formatln("@@");
                }
            }
        }
        */
        while ((str = Cin.get) != "q") {
            comms.write(str);
        }

        comms.close();

        return 0;
    }

}

==============================================

regards,

-=mike=-



More information about the Digitalmars-d-learn mailing list