From a89ebacac0a9d6450cd01d88b2645176f3c833ac Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Sat, 9 Jan 2010 13:21:59 +0000 Subject: [PATCH] support Windows in Serial.h|cpp The serial code was Linux/Mac only and utilised termio/tcsetattr functions that were not available on Windows platforms. This patch ports the serial code to the Windows APIs using the same approach as the Computrainer.cpp code. It allows us to support old-style serial download cables on Windows. (We already supported them on Mac and Linux.) --- src/Serial.cpp | 184 ++++++++++++++++++++++++++++++++++++++++++++++++- src/Serial.h | 11 +++ src/src.pro | 4 +- 3 files changed, 196 insertions(+), 3 deletions(-) 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 \