diff --git a/src/Serial.cpp b/src/Serial.cpp index bae8d2c34..b01c57592 100644 --- a/src/Serial.cpp +++ b/src/Serial.cpp @@ -17,6 +17,15 @@ */ #include "Serial.h" + +#ifdef Q_OS_WIN32 + +// WIN32 includes +#include +#include +#else + +// Linux and Mac Includes #include #include #include @@ -32,22 +41,39 @@ #include #include #include +#endif bool SerialRegistered = CommPort::addListFunction(&Serial::myListCommPorts); +#ifdef Q_OS_WIN32 +Serial::Serial(const QString &path) : path(path), isOpen(false) +{ +} +#else Serial::Serial(const QString &path) : path(path), fd(-1) { } +#endif Serial::~Serial() { - if (fd >= 0) +#ifdef Q_OS_WIN32 + if (isOpen == true) { close(); + } +#else + if (fd >= 0) close(); +#endif } bool Serial::open(QString &err) { +#ifndef Q_OS_WIN32 + + // + // Linux and Mac OSX use stdio / termio / tcsetattr + // assert(fd < 0); fd = ::open(path.toAscii().constData(), O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd < 0) { @@ -83,16 +109,81 @@ Serial::open(QString &err) assert(0); } return true; +#else + + // + // Windows uses CreateFile / DCB / SetCommState + // + + DCB deviceSettings; // serial port settings baud rate et al + COMMTIMEOUTS timeouts; // timeout settings on serial ports + + // if deviceFilename references a port above COM9 + // then we need to open "\\.\COMX" not "COMX" + QString portSpec = "\\\\.\\" + path; + wchar_t deviceFilenameW[32]; // \\.\COM32 needs 9 characters, 32 should be enough? + MultiByteToWideChar(CP_ACP, 0, portSpec.toAscii(), -1, (LPWSTR)deviceFilenameW, + sizeof(deviceFilenameW)); + + // win32 commport API + fd = CreateFile (deviceFilenameW, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_DELETE|FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + + if (fd == INVALID_HANDLE_VALUE) return isOpen = false; + + if (GetCommState (fd, &deviceSettings) == false) return isOpen = false; + + // so we've opened the comm port lets set it up for + deviceSettings.BaudRate = CBR_9600; + deviceSettings.fParity = NOPARITY; + deviceSettings.ByteSize = 8; + deviceSettings.StopBits = ONESTOPBIT; + deviceSettings.EofChar = 0x0; + deviceSettings.ErrorChar = 0x0; + deviceSettings.EvtChar = 0x0; + deviceSettings.fBinary = TRUE; + deviceSettings.fRtsControl = 0x0; + deviceSettings.fOutxCtsFlow = FALSE; + + + if (SetCommState(fd, &deviceSettings) == false) { + CloseHandle(fd); + return isOpen = false; + } + + timeouts.ReadIntervalTimeout = 0; + timeouts.ReadTotalTimeoutConstant = 5000; + timeouts.ReadTotalTimeoutMultiplier = 50; + timeouts.WriteTotalTimeoutConstant = 5000; + timeouts.WriteTotalTimeoutMultiplier = 0; + SetCommTimeouts(fd, &timeouts); + + return isOpen = true; +#endif } void Serial::close() { +#ifndef Q_OS_WIN32 assert(fd >= 0); ::close(fd); fd = -1; +#else + if (isOpen == true) { + CloseHandle(fd); + isOpen = false; + } +#endif } + +#ifndef Q_OS_WIN32 +// +// On Mac/Linux read/write timeout periods +// use timeval, whilst on Win32 the readfile +// API does this for you. +// static struct timeval & operator-=(struct timeval &left, const struct timeval &right) { @@ -114,10 +205,16 @@ operator<(struct timeval &left, const struct timeval &right) return false; return left.tv_usec < right.tv_usec; } +#endif int Serial::read(void *buf, size_t nbyte, QString &err) { +#ifndef Q_OS_WIN32 + // + // Mac and Linux use select and timevals to + // do non-blocking reads with a timeout + // assert(fd >= 0); assert(nbyte > 0); struct timeval start; @@ -152,11 +249,27 @@ Serial::read(void *buf, size_t nbyte, QString &err) assert(false); } return count; +#else + + // + // Win32 API uses readfile + // which handles timeouts / ready read + // + DWORD cBytes; + int rc = ReadFile(fd, buf, nbyte, &cBytes, NULL); + if (rc) return (int)cBytes; + else return (-1); + +#endif } int Serial::write(void *buf, size_t nbyte, QString &err) { +#ifndef Q_OS_WIN32 + // + // Max and Linux use select() and write() + // assert(fd >= 0); fd_set fdset; FD_ZERO(&fdset); @@ -177,6 +290,20 @@ Serial::write(void *buf, size_t nbyte, QString &err) if (result < 0) err = QString("write: ") + strerror(errno); return result; +#else + + // + // Windows use the writefile WIN32 API + // + DWORD cBytes; + int rc = WriteFile(fd, buf, nbyte, &cBytes, NULL); + if (!rc) { + err = QString("write: error %1").arg(rc); + return -1; + } else { + return (int)cBytes; // how much was written? + } +#endif } QString @@ -185,6 +312,10 @@ Serial::name() const return QString("Serial: ") + path; } +#ifndef Q_OS_WIN32 +// +// Linux and Mac device enumerator matches wildcards in /dev +// static int find_devices(char *result[], int capacity) { @@ -207,6 +338,57 @@ find_devices(char *result[], int capacity) } return count; } +#else +// +// Windows uses WIN32 API to open file to check existence +// +static int +find_devices(char *result[], int capacity) +{ + // it is valid to have up to 255 com devices! + int count=0; + + for (int i=1; i<256 && count < capacity; i++) { + + // longCOM is in form "\\.\COMn" which is used by the API + // shortCOM is in form COMn which is familiar to users + QString shortCOM = QString("COM%1").arg(i); + QString longCOM = "\\\\.\\" + shortCOM; + wchar_t deviceFilenameW[32]; + MultiByteToWideChar(CP_ACP, 0, longCOM.toAscii(), -1, (LPWSTR)deviceFilenameW, + sizeof(deviceFilenameW)); + + // Try to open the port + BOOL bSuccess = FALSE; + HANDLE hPort = CreateFile(deviceFilenameW, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); + + if (hPort == INVALID_HANDLE_VALUE) { + DWORD dwError = GetLastError(); + // Check to see if the error was because some other app had the port open + // or a general and valid failure + + if (dwError == ERROR_ACCESS_DENIED || dwError == ERROR_GEN_FAILURE || + dwError == ERROR_SHARING_VIOLATION || dwError == ERROR_SEM_TIMEOUT) + bSuccess = TRUE; + } else { + + // The port was opened successfully + bSuccess = TRUE; + + // Don't forget to close the port, since we are going to do nothing with it anyway + CloseHandle(hPort); + } + + // If success then add to the list + if (bSuccess) { + result[count] = (char*) malloc(shortCOM.length() + 1); // include '\0' terminator + strcpy(result[count], shortCOM.toLatin1().constData()); + count++; + } + } + return count; +} +#endif QVector Serial::myListCommPorts(QString &err) diff --git a/src/Serial.h b/src/Serial.h index ac547faca..30e12a7b3 100644 --- a/src/Serial.h +++ b/src/Serial.h @@ -21,13 +21,24 @@ #include "CommPort.h" +#ifdef Q_OS_WIN32 // for HANDLE +#include +#include +#endif + class Serial : public CommPort { Serial(const Serial &); Serial& operator=(const Serial &); QString path; +#ifndef Q_OS_WIN32 int fd; +#else + bool isOpen; // don't rely on fd value to determine if + // the COM port is open + HANDLE fd; // file descriptor for reading from com3 +#endif Serial(const QString &path); public: diff --git a/src/src.pro b/src/src.pro index 12f8e5f95..4bfed2e28 100644 --- a/src/src.pro +++ b/src/src.pro @@ -39,8 +39,6 @@ macx { !win32 { RC_FILE = images/gc.icns - HEADERS += Serial.h - SOURCES += Serial.cpp } win32 { INCLUDEPATH += ./win32 @@ -109,6 +107,7 @@ HEADERS += \ RideSummaryWindow.h \ Season.h \ SeasonParser.h \ + Serial.h \ Settings.h \ SimpleNetworkController.h \ SimpleNetworkClient.h \ @@ -190,6 +189,7 @@ SOURCES += \ RideSummaryWindow.cpp \ Season.cpp \ SeasonParser.cpp \ + Serial.cpp \ SimpleNetworkController.cpp \ SimpleNetworkClient.cpp \ SplitRideDialog.cpp \