From f03d3feaf989f43bd239bc058f9bdf9865405878 Mon Sep 17 00:00:00 2001 From: J-F Martel Date: Wed, 2 Dec 2015 20:37:30 -0500 Subject: [PATCH] dev --- Ico/icon.png | Bin 0 -> 67051 bytes .../qextserialport/posix_qextserialport.cpp | 959 ++++++++++++++++++ Sources/qextserialport/qextserialenumerator.h | 199 ++++ .../qextserialenumerator_osx.cpp | 288 ++++++ .../qextserialenumerator_unix.cpp | 75 ++ .../qextserialenumerator_win.cpp | 206 ++++ 6 files changed, 1727 insertions(+) create mode 100644 Ico/icon.png create mode 100644 Sources/qextserialport/posix_qextserialport.cpp create mode 100644 Sources/qextserialport/qextserialenumerator.h create mode 100644 Sources/qextserialport/qextserialenumerator_osx.cpp create mode 100644 Sources/qextserialport/qextserialenumerator_unix.cpp create mode 100644 Sources/qextserialport/qextserialenumerator_win.cpp diff --git a/Ico/icon.png b/Ico/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f0fd832c2e4d43c500191ac44bd18382e45f31c3 GIT binary patch literal 67051 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

Kq`YfR2JEdYnBPg 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 literal 0 HcmV?d00001 diff --git a/Sources/qextserialport/posix_qextserialport.cpp b/Sources/qextserialport/posix_qextserialport.cpp new file mode 100644 index 0000000..c7f923c --- /dev/null +++ b/Sources/qextserialport/posix_qextserialport.cpp @@ -0,0 +1,959 @@ + + +#include +#include +#include "qextserialport.h" +#include +#include + +void QextSerialPort::platformSpecificInit() +{ + fd = 0; + readNotifier = 0; +} + +/*! +Standard destructor. +*/ +void QextSerialPort::platformSpecificDestruct() +{} + +/*! +Sets the baud rate of the serial port. Note that not all rates are applicable on +all platforms. The following table shows translations of the various baud rate +constants on Windows(including NT/2000) and POSIX platforms. Speeds marked with an * +are speeds that are usable on both Windows and POSIX. + +\note +BAUD76800 may not be supported on all POSIX systems. SGI/IRIX systems do not support +BAUD1800. + +\verbatim + + RATE Windows Speed POSIX Speed + ----------- ------------- ----------- + BAUD50 110 50 + BAUD75 110 75 + *BAUD110 110 110 + BAUD134 110 134.5 + BAUD150 110 150 + BAUD200 110 200 + *BAUD300 300 300 + *BAUD600 600 600 + *BAUD1200 1200 1200 + BAUD1800 1200 1800 + *BAUD2400 2400 2400 + *BAUD4800 4800 4800 + *BAUD9600 9600 9600 + BAUD14400 14400 9600 + *BAUD19200 19200 19200 + *BAUD38400 38400 38400 + BAUD56000 56000 38400 + *BAUD57600 57600 57600 + BAUD76800 57600 76800 + *BAUD115200 115200 115200 + BAUD128000 128000 115200 + BAUD256000 256000 115200 +\endverbatim +*/ +void QextSerialPort::setBaudRate(BaudRateType baudRate) +{ + QMutexLocker lock(mutex); + if (Settings.BaudRate!=baudRate) { + switch (baudRate) { + case BAUD14400: + Settings.BaudRate=BAUD9600; + break; + + case BAUD56000: + Settings.BaudRate=BAUD38400; + break; + + case BAUD76800: + +#ifndef B76800 + Settings.BaudRate=BAUD57600; +#else + Settings.BaudRate=baudRate; +#endif + break; + + case BAUD128000: + case BAUD256000: + Settings.BaudRate=BAUD115200; + break; + + default: + Settings.BaudRate=baudRate; + break; + } + } + if (isOpen()) { + switch (baudRate) { + + /*50 baud*/ + case BAUD50: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows does not support 50 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B50; +#else + cfsetispeed(&Posix_CommConfig, B50); + cfsetospeed(&Posix_CommConfig, B50); +#endif + break; + + /*75 baud*/ + case BAUD75: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows does not support 75 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B75; +#else + cfsetispeed(&Posix_CommConfig, B75); + cfsetospeed(&Posix_CommConfig, B75); +#endif + break; + + /*110 baud*/ + case BAUD110: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B110; +#else + cfsetispeed(&Posix_CommConfig, B110); + cfsetospeed(&Posix_CommConfig, B110); +#endif + break; + + /*134.5 baud*/ + case BAUD134: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows does not support 134.5 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B134; +#else + cfsetispeed(&Posix_CommConfig, B134); + cfsetospeed(&Posix_CommConfig, B134); +#endif + break; + + /*150 baud*/ + case BAUD150: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows does not support 150 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B150; +#else + cfsetispeed(&Posix_CommConfig, B150); + cfsetospeed(&Posix_CommConfig, B150); +#endif + break; + + /*200 baud*/ + case BAUD200: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows does not support 200 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B200; +#else + cfsetispeed(&Posix_CommConfig, B200); + cfsetospeed(&Posix_CommConfig, B200); +#endif + break; + + /*300 baud*/ + case BAUD300: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B300; +#else + cfsetispeed(&Posix_CommConfig, B300); + cfsetospeed(&Posix_CommConfig, B300); +#endif + break; + + /*600 baud*/ + case BAUD600: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B600; +#else + cfsetispeed(&Posix_CommConfig, B600); + cfsetospeed(&Posix_CommConfig, B600); +#endif + break; + + /*1200 baud*/ + case BAUD1200: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B1200; +#else + cfsetispeed(&Posix_CommConfig, B1200); + cfsetospeed(&Posix_CommConfig, B1200); +#endif + break; + + /*1800 baud*/ + case BAUD1800: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows and IRIX do not support 1800 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B1800; +#else + cfsetispeed(&Posix_CommConfig, B1800); + cfsetospeed(&Posix_CommConfig, B1800); +#endif + break; + + /*2400 baud*/ + case BAUD2400: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B2400; +#else + cfsetispeed(&Posix_CommConfig, B2400); + cfsetospeed(&Posix_CommConfig, B2400); +#endif + break; + + /*4800 baud*/ + case BAUD4800: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B4800; +#else + cfsetispeed(&Posix_CommConfig, B4800); + cfsetospeed(&Posix_CommConfig, B4800); +#endif + break; + + /*9600 baud*/ + case BAUD9600: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B9600; +#else + cfsetispeed(&Posix_CommConfig, B9600); + cfsetospeed(&Posix_CommConfig, B9600); +#endif + break; + + /*14400 baud*/ + case BAUD14400: + TTY_WARNING("QextSerialPort: POSIX does not support 14400 baud operation. Switching to 9600 baud."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B9600; +#else + cfsetispeed(&Posix_CommConfig, B9600); + cfsetospeed(&Posix_CommConfig, B9600); +#endif + break; + + /*19200 baud*/ + case BAUD19200: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B19200; +#else + cfsetispeed(&Posix_CommConfig, B19200); + cfsetospeed(&Posix_CommConfig, B19200); +#endif + break; + + /*38400 baud*/ + case BAUD38400: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B38400; +#else + cfsetispeed(&Posix_CommConfig, B38400); + cfsetospeed(&Posix_CommConfig, B38400); +#endif + break; + + /*56000 baud*/ + case BAUD56000: + TTY_WARNING("QextSerialPort: POSIX does not support 56000 baud operation. Switching to 38400 baud."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B38400; +#else + cfsetispeed(&Posix_CommConfig, B38400); + cfsetospeed(&Posix_CommConfig, B38400); +#endif + break; + + /*57600 baud*/ + case BAUD57600: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B57600; +#else + cfsetispeed(&Posix_CommConfig, B57600); + cfsetospeed(&Posix_CommConfig, B57600); +#endif + break; + + /*76800 baud*/ + case BAUD76800: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows and some POSIX systems do not support 76800 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + +#ifdef B76800 + Posix_CommConfig.c_cflag|=B76800; +#else + TTY_WARNING("QextSerialPort: QextSerialPort was compiled without 76800 baud support. Switching to 57600 baud."); + Posix_CommConfig.c_cflag|=B57600; +#endif //B76800 +#else //CBAUD +#ifdef B76800 + cfsetispeed(&Posix_CommConfig, B76800); + cfsetospeed(&Posix_CommConfig, B76800); +#else + TTY_WARNING("QextSerialPort: QextSerialPort was compiled without 76800 baud support. Switching to 57600 baud."); + cfsetispeed(&Posix_CommConfig, B57600); + cfsetospeed(&Posix_CommConfig, B57600); +#endif //B76800 +#endif //CBAUD + break; + + /*115200 baud*/ + case BAUD115200: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B115200; +#else + cfsetispeed(&Posix_CommConfig, B115200); + cfsetospeed(&Posix_CommConfig, B115200); +#endif + break; + + /*128000 baud*/ + case BAUD128000: + TTY_WARNING("QextSerialPort: POSIX does not support 128000 baud operation. Switching to 115200 baud."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B115200; +#else + cfsetispeed(&Posix_CommConfig, B115200); + cfsetospeed(&Posix_CommConfig, B115200); +#endif + break; + + /*256000 baud*/ + case BAUD256000: + TTY_WARNING("QextSerialPort: POSIX does not support 256000 baud operation. Switching to 115200 baud."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B115200; +#else + cfsetispeed(&Posix_CommConfig, B115200); + cfsetospeed(&Posix_CommConfig, B115200); +#endif + break; + } + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } +} + +/*! +Sets the number of data bits used by the serial port. Possible values of dataBits are: +\verbatim + DATA_5 5 data bits + DATA_6 6 data bits + DATA_7 7 data bits + DATA_8 8 data bits +\endverbatim + +\note +This function is subject to the following restrictions: +\par + 5 data bits cannot be used with 2 stop bits. +\par + 8 data bits cannot be used with space parity on POSIX systems. +*/ +void QextSerialPort::setDataBits(DataBitsType dataBits) +{ + QMutexLocker lock(mutex); + if (Settings.DataBits!=dataBits) { + if ((Settings.StopBits==STOP_2 && dataBits==DATA_5) || + (Settings.StopBits==STOP_1_5 && dataBits!=DATA_5) || + (Settings.Parity==PAR_SPACE && dataBits==DATA_8)) { + } + else { + Settings.DataBits=dataBits; + } + } + if (isOpen()) { + switch(dataBits) { + + /*5 data bits*/ + case DATA_5: + if (Settings.StopBits==STOP_2) { + TTY_WARNING("QextSerialPort: 5 Data bits cannot be used with 2 stop bits."); + } + else { + Settings.DataBits=dataBits; + Posix_CommConfig.c_cflag&=(~CSIZE); + Posix_CommConfig.c_cflag|=CS5; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + + /*6 data bits*/ + case DATA_6: + if (Settings.StopBits==STOP_1_5) { + TTY_WARNING("QextSerialPort: 6 Data bits cannot be used with 1.5 stop bits."); + } + else { + Settings.DataBits=dataBits; + Posix_CommConfig.c_cflag&=(~CSIZE); + Posix_CommConfig.c_cflag|=CS6; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + + /*7 data bits*/ + case DATA_7: + if (Settings.StopBits==STOP_1_5) { + TTY_WARNING("QextSerialPort: 7 Data bits cannot be used with 1.5 stop bits."); + } + else { + Settings.DataBits=dataBits; + Posix_CommConfig.c_cflag&=(~CSIZE); + Posix_CommConfig.c_cflag|=CS7; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + + /*8 data bits*/ + case DATA_8: + if (Settings.StopBits==STOP_1_5) { + TTY_WARNING("QextSerialPort: 8 Data bits cannot be used with 1.5 stop bits."); + } + else { + Settings.DataBits=dataBits; + Posix_CommConfig.c_cflag&=(~CSIZE); + Posix_CommConfig.c_cflag|=CS8; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + } + } +} + +/*! +Sets the parity associated with the serial port. The possible values of parity are: +\verbatim + PAR_SPACE Space Parity + PAR_MARK Mark Parity + PAR_NONE No Parity + PAR_EVEN Even Parity + PAR_ODD Odd Parity +\endverbatim + +\note +This function is subject to the following limitations: +\par +POSIX systems do not support mark parity. +\par +POSIX systems support space parity only if tricked into doing so, and only with + fewer than 8 data bits. Use space parity very carefully with POSIX systems. +*/ +void QextSerialPort::setParity(ParityType parity) +{ + QMutexLocker lock(mutex); + if (Settings.Parity!=parity) { + if (parity==PAR_MARK || (parity==PAR_SPACE && Settings.DataBits==DATA_8)) { + } + else { + Settings.Parity=parity; + } + } + if (isOpen()) { + switch (parity) { + + /*space parity*/ + case PAR_SPACE: + if (Settings.DataBits==DATA_8) { + TTY_PORTABILITY_WARNING("QextSerialPort: Space parity is only supported in POSIX with 7 or fewer data bits"); + } + else { + + /*space parity not directly supported - add an extra data bit to simulate it*/ + Posix_CommConfig.c_cflag&=~(PARENB|CSIZE); + switch(Settings.DataBits) { + case DATA_5: + Settings.DataBits=DATA_6; + Posix_CommConfig.c_cflag|=CS6; + break; + + case DATA_6: + Settings.DataBits=DATA_7; + Posix_CommConfig.c_cflag|=CS7; + break; + + case DATA_7: + Settings.DataBits=DATA_8; + Posix_CommConfig.c_cflag|=CS8; + break; + + case DATA_8: + break; + } + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + + /*mark parity - WINDOWS ONLY*/ + case PAR_MARK: + TTY_WARNING("QextSerialPort: Mark parity is not supported by POSIX."); + break; + + /*no parity*/ + case PAR_NONE: + Posix_CommConfig.c_cflag&=(~PARENB); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + + /*even parity*/ + case PAR_EVEN: + Posix_CommConfig.c_cflag&=(~PARODD); + Posix_CommConfig.c_cflag|=PARENB; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + + /*odd parity*/ + case PAR_ODD: + Posix_CommConfig.c_cflag|=(PARENB|PARODD); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + } + } +} + +/*! +Sets the number of stop bits used by the serial port. Possible values of stopBits are: +\verbatim + STOP_1 1 stop bit + STOP_1_5 1.5 stop bits + STOP_2 2 stop bits +\endverbatim +\note +This function is subject to the following restrictions: +\par + 2 stop bits cannot be used with 5 data bits. +\par + POSIX does not support 1.5 stop bits. + +*/ +void QextSerialPort::setStopBits(StopBitsType stopBits) +{ + QMutexLocker lock(mutex); + if (Settings.StopBits!=stopBits) { + if ((Settings.DataBits==DATA_5 && stopBits==STOP_2) || stopBits==STOP_1_5) {} + else { + Settings.StopBits=stopBits; + } + } + if (isOpen()) { + switch (stopBits) { + + /*one stop bit*/ + case STOP_1: + Settings.StopBits=stopBits; + Posix_CommConfig.c_cflag&=(~CSTOPB); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + + /*1.5 stop bits*/ + case STOP_1_5: + TTY_WARNING("QextSerialPort: 1.5 stop bit operation is not supported by POSIX."); + break; + + /*two stop bits*/ + case STOP_2: + if (Settings.DataBits==DATA_5) { + TTY_WARNING("QextSerialPort: 2 stop bits cannot be used with 5 data bits"); + } + else { + Settings.StopBits=stopBits; + Posix_CommConfig.c_cflag|=CSTOPB; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + } + } +} + +/*! +Sets the flow control used by the port. Possible values of flow are: +\verbatim + FLOW_OFF No flow control + FLOW_HARDWARE Hardware (RTS/CTS) flow control + FLOW_XONXOFF Software (XON/XOFF) flow control +\endverbatim +\note +FLOW_HARDWARE may not be supported on all versions of UNIX. In cases where it is +unsupported, FLOW_HARDWARE is the same as FLOW_OFF. + +*/ +void QextSerialPort::setFlowControl(FlowType flow) +{ + QMutexLocker lock(mutex); + if (Settings.FlowControl!=flow) { + Settings.FlowControl=flow; + } + if (isOpen()) { + switch(flow) { + + /*no flow control*/ + case FLOW_OFF: + Posix_CommConfig.c_cflag&=(~CRTSCTS); + Posix_CommConfig.c_iflag&=(~(IXON|IXOFF|IXANY)); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + + /*software (XON/XOFF) flow control*/ + case FLOW_XONXOFF: + Posix_CommConfig.c_cflag&=(~CRTSCTS); + Posix_CommConfig.c_iflag|=(IXON|IXOFF|IXANY); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + + case FLOW_HARDWARE: + Posix_CommConfig.c_cflag|=CRTSCTS; + Posix_CommConfig.c_iflag&=(~(IXON|IXOFF|IXANY)); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + } + } +} + +/*! +Sets the read and write timeouts for the port to millisec milliseconds. +Note that this is a per-character timeout, i.e. the port will wait this long for each +individual character, not for the whole read operation. This timeout also applies to the +bytesWaiting() function. + +\note +POSIX does not support millisecond-level control for I/O timeout values. Any +timeout set using this function will be set to the next lowest tenth of a second for +the purposes of detecting read or write timeouts. For example a timeout of 550 milliseconds +will be seen by the class as a timeout of 500 milliseconds for the purposes of reading and +writing the port. However millisecond-level control is allowed by the select() system call, +so for example a 550-millisecond timeout will be seen as 550 milliseconds on POSIX systems for +the purpose of detecting available bytes in the read buffer. + +*/ +void QextSerialPort::setTimeout(long millisec) +{ + QMutexLocker lock(mutex); + Settings.Timeout_Millisec = millisec; + Posix_Copy_Timeout.tv_sec = millisec / 1000; + Posix_Copy_Timeout.tv_usec = millisec % 1000; + if (isOpen()) { + if (millisec == -1) + fcntl(fd, F_SETFL, O_NDELAY); + else + //O_SYNC should enable blocking ::write() + //however this seems not working on Linux 2.6.21 (works on OpenBSD 4.2) + fcntl(fd, F_SETFL, O_SYNC); + tcgetattr(fd, & Posix_CommConfig); + Posix_CommConfig.c_cc[VTIME] = millisec/100; + tcsetattr(fd, TCSAFLUSH, & Posix_CommConfig); + } +} + +/*! +Opens the serial port associated to this class. +This function has no effect if the port associated with the class is already open. +The port is also configured to the current settings, as stored in the Settings structure. +*/ +bool QextSerialPort::open(OpenMode mode) +{ + QMutexLocker lock(mutex); + if (mode == QIODevice::NotOpen) + return isOpen(); + if (!isOpen()) { + qDebug() << "trying to open file" << port.toAscii(); + //note: linux 2.6.21 seems to ignore O_NDELAY flag + if ((fd = ::open(port.toAscii() ,O_RDWR | O_NOCTTY | O_NDELAY)) != -1) { + qDebug("file opened succesfully"); + + setOpenMode(mode); // Flag the port as opened + tcgetattr(fd, &old_termios); // Save the old termios + Posix_CommConfig = old_termios; // Make a working copy + cfmakeraw(&Posix_CommConfig); // Enable raw access + + /*set up other port settings*/ + Posix_CommConfig.c_cflag|=CREAD|CLOCAL; + Posix_CommConfig.c_lflag&=(~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG)); + Posix_CommConfig.c_iflag&=(~(INPCK|IGNPAR|PARMRK|ISTRIP|ICRNL|IXANY)); + Posix_CommConfig.c_oflag&=(~OPOST); + Posix_CommConfig.c_cc[VMIN]= 0; +#ifdef _POSIX_VDISABLE // Is a disable character available on this system? + // Some systems allow for per-device disable-characters, so get the + // proper value for the configured device + const long vdisable = fpathconf(fd, _PC_VDISABLE); + Posix_CommConfig.c_cc[VINTR] = vdisable; + Posix_CommConfig.c_cc[VQUIT] = vdisable; + Posix_CommConfig.c_cc[VSTART] = vdisable; + Posix_CommConfig.c_cc[VSTOP] = vdisable; + Posix_CommConfig.c_cc[VSUSP] = vdisable; +#endif //_POSIX_VDISABLE + setBaudRate(Settings.BaudRate); + setDataBits(Settings.DataBits); + setParity(Settings.Parity); + setStopBits(Settings.StopBits); + setFlowControl(Settings.FlowControl); + setTimeout(Settings.Timeout_Millisec); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + + if (queryMode() == QextSerialPort::EventDriven) { + readNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + connect(readNotifier, SIGNAL(activated(int)), this, SIGNAL(readyRead())); + } + } else { + qDebug() << "could not open file:" << strerror(errno); + lastErr = E_FILE_NOT_FOUND; + } + } + return isOpen(); +} + +/*! +Closes a serial port. This function has no effect if the serial port associated with the class +is not currently open. +*/ +void QextSerialPort::close() +{ + QMutexLocker lock(mutex); + if( isOpen() ) + { + // Force a flush and then restore the original termios + flush(); + // Using both TCSAFLUSH and TCSANOW here discards any pending input + tcsetattr(fd, TCSAFLUSH | TCSANOW, &old_termios); // Restore termios + // Be a good QIODevice and call QIODevice::close() before POSIX close() + // so the aboutToClose() signal is emitted at the proper time + QIODevice::close(); // Flag the device as closed + // QIODevice::close() doesn't actually close the port, so do that here + ::close(fd); + if(readNotifier) { + delete readNotifier; + readNotifier = 0; + } + } +} + +/*! +Flushes all pending I/O to the serial port. This function has no effect if the serial port +associated with the class is not currently open. +*/ +void QextSerialPort::flush() +{ + QMutexLocker lock(mutex); + if (isOpen()) + tcflush(fd, TCIOFLUSH); +} + +/*! +This function will return the number of bytes waiting in the receive queue of the serial port. +It is included primarily to provide a complete QIODevice interface, and will not record errors +in the lastErr member (because it is const). This function is also not thread-safe - in +multithreading situations, use QextSerialPort::bytesWaiting() instead. +*/ +qint64 QextSerialPort::size() const +{ + int numBytes; + if (ioctl(fd, FIONREAD, &numBytes)<0) { + numBytes = 0; + } + return (qint64)numBytes; +} + +/*! +Returns the number of bytes waiting in the port's receive queue. This function will return 0 if +the port is not currently open, or -1 on error. +*/ +qint64 QextSerialPort::bytesAvailable() const +{ + QMutexLocker lock(mutex); + if (isOpen()) { + int bytesQueued; + if (ioctl(fd, FIONREAD, &bytesQueued) == -1) { + return (qint64)-1; + } + return bytesQueued + QIODevice::bytesAvailable(); + } + return 0; +} + +/*! +This function is included to implement the full QIODevice interface, and currently has no +purpose within this class. This function is meaningless on an unbuffered device and currently +only prints a warning message to that effect. +*/ +void QextSerialPort::ungetChar(char) +{ + /*meaningless on unbuffered sequential device - return error and print a warning*/ + TTY_WARNING("QextSerialPort: ungetChar() called on an unbuffered sequential device - operation is meaningless"); +} + +/*! +Translates a system-specific error code to a QextSerialPort error code. Used internally. +*/ +void QextSerialPort::translateError(ulong error) +{ + switch (error) { + case EBADF: + case ENOTTY: + lastErr=E_INVALID_FD; + break; + + case EINTR: + lastErr=E_CAUGHT_NON_BLOCKED_SIGNAL; + break; + + case ENOMEM: + lastErr=E_NO_MEMORY; + break; + } +} + +/*! +Sets DTR line to the requested state (high by default). This function will have no effect if +the port associated with the class is not currently open. +*/ +void QextSerialPort::setDtr(bool set) +{ + QMutexLocker lock(mutex); + if (isOpen()) { + int status; + ioctl(fd, TIOCMGET, &status); + if (set) { + status|=TIOCM_DTR; + } + else { + status&=~TIOCM_DTR; + } + ioctl(fd, TIOCMSET, &status); + } +} + +/*! +Sets RTS line to the requested state (high by default). This function will have no effect if +the port associated with the class is not currently open. +*/ +void QextSerialPort::setRts(bool set) +{ + QMutexLocker lock(mutex); + if (isOpen()) { + int status; + ioctl(fd, TIOCMGET, &status); + if (set) { + status|=TIOCM_RTS; + } + else { + status&=~TIOCM_RTS; + } + ioctl(fd, TIOCMSET, &status); + } +} + +/*! +Returns the line status as stored by the port function. This function will retrieve the states +of the following lines: DCD, CTS, DSR, and RI. On POSIX systems, the following additional lines +can be monitored: DTR, RTS, Secondary TXD, and Secondary RXD. The value returned is an unsigned +long with specific bits indicating which lines are high. The following constants should be used +to examine the states of individual lines: + +\verbatim +Mask Line +------ ---- +LS_CTS CTS +LS_DSR DSR +LS_DCD DCD +LS_RI RI +LS_RTS RTS (POSIX only) +LS_DTR DTR (POSIX only) +LS_ST Secondary TXD (POSIX only) +LS_SR Secondary RXD (POSIX only) +\endverbatim + +This function will return 0 if the port associated with the class is not currently open. +*/ +unsigned long QextSerialPort::lineStatus() +{ + unsigned long Status=0, Temp=0; + QMutexLocker lock(mutex); + if (isOpen()) { + ioctl(fd, TIOCMGET, &Temp); + if (Temp&TIOCM_CTS) { + Status|=LS_CTS; + } + if (Temp&TIOCM_DSR) { + Status|=LS_DSR; + } + if (Temp&TIOCM_RI) { + Status|=LS_RI; + } + if (Temp&TIOCM_CD) { + Status|=LS_DCD; + } + if (Temp&TIOCM_DTR) { + Status|=LS_DTR; + } + if (Temp&TIOCM_RTS) { + Status|=LS_RTS; + } + if (Temp&TIOCM_ST) { + Status|=LS_ST; + } + if (Temp&TIOCM_SR) { + Status|=LS_SR; + } + } + return Status; +} + +/*! +Reads a block of data from the serial port. This function will read at most maxSize bytes from +the serial port and place them in the buffer pointed to by data. Return value is the number of +bytes actually read, or -1 on error. + +\warning before calling this function ensure that serial port associated with this class +is currently open (use isOpen() function to check if port is open). +*/ +qint64 QextSerialPort::readData(char * data, qint64 maxSize) +{ + QMutexLocker lock(mutex); + int retVal = ::read(fd, data, maxSize); + if (retVal == -1) + lastErr = E_READ_FAILED; + + return retVal; +} + +/*! +Writes a block of data to the serial port. This function will write maxSize bytes +from the buffer pointed to by data to the serial port. Return value is the number +of bytes actually written, or -1 on error. + +\warning before calling this function ensure that serial port associated with this class +is currently open (use isOpen() function to check if port is open). +*/ +qint64 QextSerialPort::writeData(const char * data, qint64 maxSize) +{ + QMutexLocker lock(mutex); + int retVal = ::write(fd, data, maxSize); + if (retVal == -1) + lastErr = E_WRITE_FAILED; + + return (qint64)retVal; +} diff --git a/Sources/qextserialport/qextserialenumerator.h b/Sources/qextserialport/qextserialenumerator.h new file mode 100644 index 0000000..329648e --- /dev/null +++ b/Sources/qextserialport/qextserialenumerator.h @@ -0,0 +1,199 @@ +/*! + * \file qextserialenumerator.h + * \author Michal Policht + * \see QextSerialEnumerator + */ + +#ifndef _QEXTSERIALENUMERATOR_H_ +#define _QEXTSERIALENUMERATOR_H_ + + +#include +#include +#include +#include "qextserialport_global.h" + +#ifdef Q_OS_WIN + #include + #include + #include +#endif /*Q_OS_WIN*/ + +#ifdef Q_OS_MAC + #include +#endif + +/*! + * Structure containing port information. + */ +struct QextPortInfo { + QString portName; ///< Port name. + QString physName; ///< Physical name. + QString friendName; ///< Friendly name. + QString enumName; ///< Enumerator name. + int vendorID; ///< Vendor ID. + int productID; ///< Product ID +}; + +#ifdef Q_OS_WIN +#ifdef QT_GUI_LIB +#include +class QextSerialEnumerator; + +class QextSerialRegistrationWidget : public QWidget +{ + Q_OBJECT + public: + QextSerialRegistrationWidget( QextSerialEnumerator* qese ) { + this->qese = qese; + } + ~QextSerialRegistrationWidget( ) { } + + protected: + QextSerialEnumerator* qese; + bool winEvent( MSG* message, long* result ); +}; +#endif // QT_GUI_LIB +#endif // Q_OS_WIN + +/*! + Provides list of ports available in the system. + + \section Usage + To poll the system for a list of connected devices, simply use getPorts(). Each + QextPortInfo structure will populated with information about the corresponding device. + + \b Example + \code + QList ports = QextSerialEnumerator::getPorts(); + foreach( QextPortInfo port, ports ) { + // inspect port... + } + \endcode + + To enable event-driven notification of device connection events, first call + setUpNotifications() and then connect to the deviceDiscovered() and deviceRemoved() + signals. Event-driven behavior is currently available only on Windows and OS X. + + \b Example + \code + QextSerialEnumerator* enumerator = new QextSerialEnumerator(); + connect(enumerator, SIGNAL(deviceDiscovered(const QextPortInfo &)), + myClass, SLOT(onDeviceDiscovered(const QextPortInfo &))); + connect(enumerator, SIGNAL(deviceRemoved(const QextPortInfo &)), + myClass, SLOT(onDeviceRemoved(const QextPortInfo &))); + \endcode + + \section Credits + Windows implementation is based on Zach Gorman's work from + The Code Project (http://www.codeproject.com/system/setupdi.asp). + + OS X implementation, see + http://developer.apple.com/documentation/DeviceDrivers/Conceptual/AccessingHardware/AH_Finding_Devices/chapter_4_section_2.html + + \author Michal Policht, Liam Staskawicz +*/ +class QEXTSERIALPORT_EXPORT QextSerialEnumerator : public QObject +{ +Q_OBJECT + public: + QextSerialEnumerator( ); + ~QextSerialEnumerator( ); + + #ifdef Q_OS_WIN + LRESULT onDeviceChangeWin( WPARAM wParam, LPARAM lParam ); + private: + /*! + * Get value of specified property from the registry. + * \param key handle to an open key. + * \param property property name. + * \return property value. + */ + static QString getRegKeyValue(HKEY key, LPCTSTR property); + + /*! + * Get specific property from registry. + * \param devInfo pointer to the device information set that contains the interface + * and its underlying device. Returned by SetupDiGetClassDevs() function. + * \param devData pointer to an SP_DEVINFO_DATA structure that defines the device instance. + * this is returned by SetupDiGetDeviceInterfaceDetail() function. + * \param property registry property. One of defined SPDRP_* constants. + * \return property string. + */ + static QString getDeviceProperty(HDEVINFO devInfo, PSP_DEVINFO_DATA devData, DWORD property); + + /*! + * Search for serial ports using setupapi. + * \param infoList list with result. + */ + static void setupAPIScan(QList & infoList); + void setUpNotificationWin( ); + static bool getDeviceDetailsWin( QextPortInfo* portInfo, HDEVINFO devInfo, + PSP_DEVINFO_DATA devData, WPARAM wParam = DBT_DEVICEARRIVAL ); + static void enumerateDevicesWin( const GUID & guidDev, QList* infoList ); + bool matchAndDispatchChangedDevice(const QString & deviceID, const GUID & guid, WPARAM wParam); + #ifdef QT_GUI_LIB + QextSerialRegistrationWidget* notificationWidget; + #endif + #endif /*Q_OS_WIN*/ + + #ifdef Q_OS_UNIX + #ifdef Q_OS_MAC + private: + /*! + * Search for serial ports using IOKit. + * \param infoList list with result. + */ + static void scanPortsOSX(QList & infoList); + static void iterateServicesOSX(io_object_t service, QList & infoList); + static bool getServiceDetailsOSX( io_object_t service, QextPortInfo* portInfo ); + + void setUpNotificationOSX( ); + void onDeviceDiscoveredOSX( io_object_t service ); + void onDeviceTerminatedOSX( io_object_t service ); + friend void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); + friend void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); + + IONotificationPortRef notificationPortRef; + + #else // Q_OS_MAC + private: + /*! + * Search for serial ports on unix. + * \param infoList list with result. + */ + static void scanPortsNix(QList & infoList); + #endif // Q_OS_MAC + #endif /* Q_OS_UNIX */ + + public: + /*! + Get list of ports. + \return list of ports currently available in the system. + */ + static QList getPorts(); + /*! + Enable event-driven notifications of board discovery/removal. + */ + void setUpNotifications( ); + + signals: + /*! + A new device has been connected to the system. + + setUpNotifications() must be called first to enable event-driven device notifications. + Currently only implemented on Windows and OS X. + \param info The device that has been discovered. + */ + void deviceDiscovered( const QextPortInfo & info ); + /*! + A device has been disconnected from the system. + + setUpNotifications() must be called first to enable event-driven device notifications. + Currently only implemented on Windows and OS X. + \param info The device that was disconnected. + */ + void deviceRemoved( const QextPortInfo & info ); +}; + +#endif /*_QEXTSERIALENUMERATOR_H_*/ diff --git a/Sources/qextserialport/qextserialenumerator_osx.cpp b/Sources/qextserialport/qextserialenumerator_osx.cpp new file mode 100644 index 0000000..229d73f --- /dev/null +++ b/Sources/qextserialport/qextserialenumerator_osx.cpp @@ -0,0 +1,288 @@ + + + +#include "qextserialenumerator.h" +#include +#include + +#include +#include +#include + +QextSerialEnumerator::QextSerialEnumerator( ) +{ + if( !QMetaType::isRegistered( QMetaType::type("QextPortInfo") ) ) + qRegisterMetaType("QextPortInfo"); +} + +QextSerialEnumerator::~QextSerialEnumerator( ) +{ + IONotificationPortDestroy( notificationPortRef ); +} + +// static +QList QextSerialEnumerator::getPorts() +{ + QList infoList; + io_iterator_t serialPortIterator = 0; + kern_return_t kernResult = KERN_FAILURE; + CFMutableDictionaryRef matchingDictionary; + + // first try to get any serialbsd devices, then try any USBCDC devices + if( !(matchingDictionary = IOServiceMatching(kIOSerialBSDServiceValue) ) ) { + qWarning("IOServiceMatching returned a NULL dictionary."); + return infoList; + } + CFDictionaryAddValue(matchingDictionary, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes)); + + // then create the iterator with all the matching devices + if( IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &serialPortIterator) != KERN_SUCCESS ) { + qCritical() << "IOServiceGetMatchingServices failed, returned" << kernResult; + return infoList; + } + iterateServicesOSX(serialPortIterator, infoList); + IOObjectRelease(serialPortIterator); + serialPortIterator = 0; + + if( !(matchingDictionary = IOServiceNameMatching("AppleUSBCDC")) ) { + qWarning("IOServiceNameMatching returned a NULL dictionary."); + return infoList; + } + + if( IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &serialPortIterator) != KERN_SUCCESS ) { + qCritical() << "IOServiceGetMatchingServices failed, returned" << kernResult; + return infoList; + } + iterateServicesOSX(serialPortIterator, infoList); + IOObjectRelease(serialPortIterator); + + return infoList; +} + +void QextSerialEnumerator::iterateServicesOSX(io_object_t service, QList & infoList) +{ + // Iterate through all modems found. + io_object_t usbService; + while( ( usbService = IOIteratorNext(service) ) ) + { + QextPortInfo info; + info.vendorID = 0; + info.productID = 0; + getServiceDetailsOSX( usbService, &info ); + infoList.append(info); + } +} + +bool QextSerialEnumerator::getServiceDetailsOSX( io_object_t service, QextPortInfo* portInfo ) +{ + bool retval = true; + CFTypeRef bsdPathAsCFString = NULL; + CFTypeRef productNameAsCFString = NULL; + CFTypeRef vendorIdAsCFNumber = NULL; + CFTypeRef productIdAsCFNumber = NULL; + // check the name of the modem's callout device + bsdPathAsCFString = IORegistryEntryCreateCFProperty(service, CFSTR(kIOCalloutDeviceKey), + kCFAllocatorDefault, 0); + + // wander up the hierarchy until we find the level that can give us the + // vendor/product IDs and the product name, if available + io_registry_entry_t parent; + kern_return_t kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parent); + while( kernResult == KERN_SUCCESS && !vendorIdAsCFNumber && !productIdAsCFNumber ) + { + if(!productNameAsCFString) + productNameAsCFString = IORegistryEntrySearchCFProperty(parent, + kIOServicePlane, + CFSTR("Product Name"), + kCFAllocatorDefault, 0); + vendorIdAsCFNumber = IORegistryEntrySearchCFProperty(parent, + kIOServicePlane, + CFSTR(kUSBVendorID), + kCFAllocatorDefault, 0); + productIdAsCFNumber = IORegistryEntrySearchCFProperty(parent, + kIOServicePlane, + CFSTR(kUSBProductID), + kCFAllocatorDefault, 0); + io_registry_entry_t oldparent = parent; + kernResult = IORegistryEntryGetParentEntry(parent, kIOServicePlane, &parent); + IOObjectRelease(oldparent); + } + + io_string_t ioPathName; + IORegistryEntryGetPath( service, kIOServicePlane, ioPathName ); + portInfo->physName = ioPathName; + + if( bsdPathAsCFString ) + { + char path[MAXPATHLEN]; + if( CFStringGetCString((CFStringRef)bsdPathAsCFString, path, + PATH_MAX, kCFStringEncodingUTF8) ) + portInfo->portName = path; + CFRelease(bsdPathAsCFString); + } + + if(productNameAsCFString) + { + char productName[MAXPATHLEN]; + if( CFStringGetCString((CFStringRef)productNameAsCFString, productName, + PATH_MAX, kCFStringEncodingUTF8) ) + portInfo->friendName = productName; + CFRelease(productNameAsCFString); + } + + if(vendorIdAsCFNumber) + { + SInt32 vID; + if(CFNumberGetValue((CFNumberRef)vendorIdAsCFNumber, kCFNumberSInt32Type, &vID)) + portInfo->vendorID = vID; + CFRelease(vendorIdAsCFNumber); + } + + if(productIdAsCFNumber) + { + SInt32 pID; + if(CFNumberGetValue((CFNumberRef)productIdAsCFNumber, kCFNumberSInt32Type, &pID)) + portInfo->productID = pID; + CFRelease(productIdAsCFNumber); + } + IOObjectRelease(service); + return retval; +} + +// IOKit callbacks registered via setupNotifications() +void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); +void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); + +void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ) +{ + QextSerialEnumerator* qese = (QextSerialEnumerator*)ctxt; + io_object_t serialService; + while ((serialService = IOIteratorNext(serialPortIterator))) + qese->onDeviceDiscoveredOSX(serialService); +} + +void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ) +{ + QextSerialEnumerator* qese = (QextSerialEnumerator*)ctxt; + io_object_t serialService; + while ((serialService = IOIteratorNext(serialPortIterator))) + qese->onDeviceTerminatedOSX(serialService); +} + +/* + A device has been discovered via IOKit. + Create a QextPortInfo if possible, and emit the signal indicating that we've found it. +*/ +void QextSerialEnumerator::onDeviceDiscoveredOSX( io_object_t service ) +{ + QextPortInfo info; + info.vendorID = 0; + info.productID = 0; + if( getServiceDetailsOSX( service, &info ) ) + emit deviceDiscovered( info ); +} + +/* + Notification via IOKit that a device has been removed. + Create a QextPortInfo if possible, and emit the signal indicating that it's gone. +*/ +void QextSerialEnumerator::onDeviceTerminatedOSX( io_object_t service ) +{ + QextPortInfo info; + info.vendorID = 0; + info.productID = 0; + if( getServiceDetailsOSX( service, &info ) ) + emit deviceRemoved( info ); +} + +/* + Create matching dictionaries for the devices we want to get notifications for, + and add them to the current run loop. Invoke the callbacks that will be responding + to these notifications once to arm them, and discover any devices that + are currently connected at the time notifications are setup. +*/ +void QextSerialEnumerator::setUpNotifications( ) +{ + kern_return_t kernResult; + mach_port_t masterPort; + CFRunLoopSourceRef notificationRunLoopSource; + CFMutableDictionaryRef classesToMatch; + CFMutableDictionaryRef cdcClassesToMatch; + io_iterator_t portIterator; + + kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort); + if (KERN_SUCCESS != kernResult) { + qDebug() << "IOMasterPort returned:" << kernResult; + return; + } + + classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); + if (classesToMatch == NULL) + qDebug("IOServiceMatching returned a NULL dictionary."); + else + CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes)); + + if( !(cdcClassesToMatch = IOServiceNameMatching("AppleUSBCDC") ) ) { + qWarning("couldn't create cdc matching dict"); + return; + } + + // Retain an additional reference since each call to IOServiceAddMatchingNotification consumes one. + classesToMatch = (CFMutableDictionaryRef) CFRetain(classesToMatch); + cdcClassesToMatch = (CFMutableDictionaryRef) CFRetain(cdcClassesToMatch); + + notificationPortRef = IONotificationPortCreate(masterPort); + if(notificationPortRef == NULL) { + qDebug("IONotificationPortCreate return a NULL IONotificationPortRef."); + return; + } + + notificationRunLoopSource = IONotificationPortGetRunLoopSource(notificationPortRef); + if (notificationRunLoopSource == NULL) { + qDebug("IONotificationPortGetRunLoopSource returned NULL CFRunLoopSourceRef."); + return; + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), notificationRunLoopSource, kCFRunLoopDefaultMode); + + kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOMatchedNotification, classesToMatch, + deviceDiscoveredCallbackOSX, this, &portIterator); + if (kernResult != KERN_SUCCESS) { + qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; + return; + } + + // arm the callback, and grab any devices that are already connected + deviceDiscoveredCallbackOSX( this, portIterator ); + + kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOMatchedNotification, cdcClassesToMatch, + deviceDiscoveredCallbackOSX, this, &portIterator); + if (kernResult != KERN_SUCCESS) { + qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; + return; + } + + // arm the callback, and grab any devices that are already connected + deviceDiscoveredCallbackOSX( this, portIterator ); + + kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOTerminatedNotification, classesToMatch, + deviceTerminatedCallbackOSX, this, &portIterator); + if (kernResult != KERN_SUCCESS) { + qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; + return; + } + + // arm the callback, and clear any devices that are terminated + deviceTerminatedCallbackOSX( this, portIterator ); + + kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOTerminatedNotification, cdcClassesToMatch, + deviceTerminatedCallbackOSX, this, &portIterator); + if (kernResult != KERN_SUCCESS) { + qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; + return; + } + + // arm the callback, and clear any devices that are terminated + deviceTerminatedCallbackOSX( this, portIterator ); +} + diff --git a/Sources/qextserialport/qextserialenumerator_unix.cpp b/Sources/qextserialport/qextserialenumerator_unix.cpp new file mode 100644 index 0000000..f43a75b --- /dev/null +++ b/Sources/qextserialport/qextserialenumerator_unix.cpp @@ -0,0 +1,75 @@ + + + +#include "qextserialenumerator.h" +#include +#include +#include +#include + +QextSerialEnumerator::QextSerialEnumerator( ) +{ + if( !QMetaType::isRegistered( QMetaType::type("QextPortInfo") ) ) + qRegisterMetaType("QextPortInfo"); +} + +QextSerialEnumerator::~QextSerialEnumerator( ) +{ +} + +QList QextSerialEnumerator::getPorts() +{ + QList infoList; +#ifdef Q_OS_LINUX + QStringList portNamePrefixes, portNameList; + portNamePrefixes << "ttyS*"; // list normal serial ports first + + QDir dir("/dev"); + portNameList = dir.entryList(portNamePrefixes, (QDir::System | QDir::Files), QDir::Name); + + // remove the values which are not serial ports for e.g. /dev/ttysa + for (int i = 0; i < portNameList.size(); i++) { + bool ok; + QString current = portNameList.at(i); + // remove the ttyS part, and check, if the other part is a number + current.remove(0,4).toInt(&ok, 10); + if (!ok) { + portNameList.removeAt(i); + i--; + } + } + + // get the non standard serial ports names + // (USB-serial, bluetooth-serial, 18F PICs, and so on) + // if you know an other name prefix for serial ports please let us know + portNamePrefixes.clear(); + portNamePrefixes << "ttyACM*" << "ttyUSB*" << "rfcomm*"; + portNameList.append(dir.entryList(portNamePrefixes, (QDir::System | QDir::Files), QDir::Name)); + + foreach (QString str , portNameList) { + QextPortInfo inf; + inf.physName = "/dev/"+str; + inf.portName = str; + + if (str.contains("ttyS")) { + inf.friendName = "Serial port "+str.remove(0, 4); + } + else if (str.contains("ttyUSB")) { + inf.friendName = "USB-serial adapter "+str.remove(0, 6); + } + else if (str.contains("rfcomm")) { + inf.friendName = "Bluetooth-serial adapter "+str.remove(0, 6); + } + inf.enumName = "/dev"; // is there a more helpful name for this? + infoList.append(inf); + } +#else + qCritical("Enumeration for POSIX systems (except Linux) is not implemented yet."); +#endif + return infoList; +} + +void QextSerialEnumerator::setUpNotifications( ) +{ + qCritical("Notifications for *Nix/FreeBSD are not implemented yet"); +} diff --git a/Sources/qextserialport/qextserialenumerator_win.cpp b/Sources/qextserialport/qextserialenumerator_win.cpp new file mode 100644 index 0000000..e2ef78c --- /dev/null +++ b/Sources/qextserialport/qextserialenumerator_win.cpp @@ -0,0 +1,206 @@ + + + +#include "qextserialenumerator.h" +#include +#include + +#include +#include +#include "qextserialport.h" +#include + +QextSerialEnumerator::QextSerialEnumerator( ) +{ + if( !QMetaType::isRegistered( QMetaType::type("QextPortInfo") ) ) + qRegisterMetaType("QextPortInfo"); +#if (defined QT_GUI_LIB) + notificationWidget = 0; +#endif // Q_OS_WIN +} + +QextSerialEnumerator::~QextSerialEnumerator( ) +{ +#if (defined QT_GUI_LIB) + if( notificationWidget ) + delete notificationWidget; +#endif +} + + + +// see http://msdn.microsoft.com/en-us/library/ms791134.aspx for list of GUID classes +#ifndef GUID_DEVCLASS_PORTS + DEFINE_GUID(GUID_DEVCLASS_PORTS, 0x4D36E978, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 ); +#endif + +/* Gordon Schumacher's macros for TCHAR -> QString conversions and vice versa */ +#ifdef UNICODE + #define QStringToTCHAR(x) (wchar_t*) x.utf16() + #define PQStringToTCHAR(x) (wchar_t*) x->utf16() + #define TCHARToQString(x) QString::fromUtf16((ushort*)(x)) + #define TCHARToQStringN(x,y) QString::fromUtf16((ushort*)(x),(y)) +#else + #define QStringToTCHAR(x) x.local8Bit().constData() + #define PQStringToTCHAR(x) x->local8Bit().constData() + #define TCHARToQString(x) QString::fromLocal8Bit((x)) + #define TCHARToQStringN(x,y) QString::fromLocal8Bit((x),(y)) +#endif /*UNICODE*/ + + +//static +QString QextSerialEnumerator::getRegKeyValue(HKEY key, LPCTSTR property) +{ + DWORD size = 0; + DWORD type; + RegQueryValueEx(key, property, NULL, NULL, NULL, & size); + BYTE* buff = new BYTE[size]; + QString result; + if( RegQueryValueEx(key, property, NULL, &type, buff, & size) == ERROR_SUCCESS ) + result = TCHARToQString((const char *)buff); + RegCloseKey(key); + delete [] buff; + return result; +} + +//static +QString QextSerialEnumerator::getDeviceProperty(HDEVINFO devInfo, PSP_DEVINFO_DATA devData, DWORD property) +{ + DWORD buffSize = 0; + SetupDiGetDeviceRegistryProperty(devInfo, devData, property, NULL, NULL, 0, & buffSize); + BYTE* buff = new BYTE[buffSize]; + SetupDiGetDeviceRegistryProperty(devInfo, devData, property, NULL, buff, buffSize, NULL); + QString result = TCHARToQString((const char *)buff); + delete [] buff; + return result; +} + +QList QextSerialEnumerator::getPorts() +{ + QList ports; + enumerateDevicesWin(GUID_DEVCLASS_PORTS, &ports); + return ports; +} + +void QextSerialEnumerator::enumerateDevicesWin( const GUID & guid, QList* infoList ) +{ + HDEVINFO devInfo; + if( (devInfo = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT)) != INVALID_HANDLE_VALUE) + { + SP_DEVINFO_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + for(int i = 0; SetupDiEnumDeviceInfo(devInfo, i, &devInfoData); i++) + { + QextPortInfo info; + info.productID = info.vendorID = 0; + getDeviceDetailsWin( &info, devInfo, &devInfoData ); + infoList->append(info); + } + SetupDiDestroyDeviceInfoList(devInfo); + } +} + +#ifdef QT_GUI_LIB +bool QextSerialRegistrationWidget::winEvent( MSG* message, long* result ) +{ + if ( message->message == WM_DEVICECHANGE ) { + qese->onDeviceChangeWin( message->wParam, message->lParam ); + *result = 1; + return true; + } + return false; +} +#endif + +void QextSerialEnumerator::setUpNotifications( ) +{ + #ifdef QT_GUI_LIB + if(notificationWidget) + return; + notificationWidget = new QextSerialRegistrationWidget(this); + + DEV_BROADCAST_DEVICEINTERFACE dbh; + ZeroMemory(&dbh, sizeof(dbh)); + dbh.dbcc_size = sizeof(dbh); + dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + CopyMemory(&dbh.dbcc_classguid, &GUID_DEVCLASS_PORTS, sizeof(GUID)); + if( RegisterDeviceNotification( notificationWidget->winId( ), &dbh, DEVICE_NOTIFY_WINDOW_HANDLE ) == NULL) + qWarning() << "RegisterDeviceNotification failed:" << GetLastError(); + // setting up notifications doesn't tell us about devices already connected + // so get those manually + foreach( QextPortInfo port, getPorts() ) + emit deviceDiscovered( port ); + #else + qWarning("QextSerialEnumerator: GUI not enabled - can't register for device notifications."); + #endif // QT_GUI_LIB +} + +LRESULT QextSerialEnumerator::onDeviceChangeWin( WPARAM wParam, LPARAM lParam ) +{ + if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam ) + { + PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam; + if( pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE ) + { + PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr; + // delimiters are different across APIs...change to backslash. ugh. + QString deviceID = TCHARToQString(pDevInf->dbcc_name).toUpper().replace("#", "\\"); + + matchAndDispatchChangedDevice(deviceID, GUID_DEVCLASS_PORTS, wParam); + } + } + return 0; +} + +bool QextSerialEnumerator::matchAndDispatchChangedDevice(const QString & deviceID, const GUID & guid, WPARAM wParam) +{ + bool rv = false; + DWORD dwFlag = (DBT_DEVICEARRIVAL == wParam) ? DIGCF_PRESENT : DIGCF_ALLCLASSES; + HDEVINFO devInfo; + if( (devInfo = SetupDiGetClassDevs(&guid,NULL,NULL,dwFlag)) != INVALID_HANDLE_VALUE ) + { + SP_DEVINFO_DATA spDevInfoData; + spDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + for(int i=0; SetupDiEnumDeviceInfo(devInfo, i, &spDevInfoData); i++) + { + DWORD nSize=0 ; + TCHAR buf[MAX_PATH]; + if ( SetupDiGetDeviceInstanceId(devInfo, &spDevInfoData, buf, MAX_PATH, &nSize) && + deviceID.contains(TCHARToQString(buf))) // we found a match + { + rv = true; + QextPortInfo info; + info.productID = info.vendorID = 0; + getDeviceDetailsWin( &info, devInfo, &spDevInfoData, wParam ); + if( wParam == DBT_DEVICEARRIVAL ) + emit deviceDiscovered(info); + else if( wParam == DBT_DEVICEREMOVECOMPLETE ) + emit deviceRemoved(info); + break; + } + } + SetupDiDestroyDeviceInfoList(devInfo); + } + return rv; +} + +bool QextSerialEnumerator::getDeviceDetailsWin( QextPortInfo* portInfo, HDEVINFO devInfo, PSP_DEVINFO_DATA devData, WPARAM wParam ) +{ + portInfo->friendName = getDeviceProperty(devInfo, devData, SPDRP_FRIENDLYNAME); + if( wParam == DBT_DEVICEARRIVAL) + portInfo->physName = getDeviceProperty(devInfo, devData, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME); + portInfo->enumName = getDeviceProperty(devInfo, devData, SPDRP_ENUMERATOR_NAME); + QString hardwareIDs = getDeviceProperty(devInfo, devData, SPDRP_HARDWAREID); + HKEY devKey = SetupDiOpenDevRegKey(devInfo, devData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); + portInfo->portName = QextSerialPort::fullPortNameWin( getRegKeyValue(devKey, TEXT("PortName")) ); + QRegExp idRx("VID_(\\w+)&PID_(\\w+)"); + if( hardwareIDs.toUpper().contains(idRx) ) + { + bool dummy; + portInfo->vendorID = idRx.cap(1).toInt(&dummy, 16); + portInfo->productID = idRx.cap(2).toInt(&dummy, 16); + //qDebug() << "got vid:" << vid << "pid:" << pid; + } + return true; +} +