mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 08:38:45 +00:00
445 lines
12 KiB
C++
445 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "Serial.h"
|
|
|
|
#ifdef Q_OS_WIN32
|
|
|
|
// WIN32 includes
|
|
#include <windows.h>
|
|
#include <winbase.h>
|
|
#else
|
|
|
|
// Linux and Mac Includes
|
|
#include <assert.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
#include <regex.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
bool SerialRegistered = CommPort::addListFunction(&Serial::myListCommPorts);
|
|
|
|
#ifdef Q_OS_WIN32
|
|
Serial::Serial(const QString &path) : CommPort("Serial"), path(path), _isOpen(false)
|
|
{
|
|
}
|
|
#else
|
|
Serial::Serial(const QString &path) : CommPort("Serial"), path(path), fd(-1)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
Serial::~Serial()
|
|
{
|
|
if( isOpen() )
|
|
close();
|
|
}
|
|
|
|
bool
|
|
Serial::isOpen()
|
|
{
|
|
#ifdef Q_OS_WIN32
|
|
return _isOpen;
|
|
#else
|
|
return fd >= 0;
|
|
#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) {
|
|
err = QString("open: ") + strerror(errno);
|
|
return false;
|
|
}
|
|
struct termios tty;
|
|
int flags = fcntl(fd, F_GETFL, 0);
|
|
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
|
perror("fcntl");
|
|
assert(0);
|
|
}
|
|
if (tcgetattr(fd, &tty) == -1) {
|
|
perror("tcgetattr");
|
|
assert(0);
|
|
}
|
|
tty.c_cflag &= ~CRTSCTS; /* no hardware flow control */
|
|
tty.c_cflag &= ~(PARENB | PARODD); /* no parity */
|
|
tty.c_cflag &= ~CSTOPB; /* 1 stop bit */
|
|
tty.c_cflag &= ~CSIZE; /* clear size bits */
|
|
tty.c_cflag |= CS8; /* 8 bits */
|
|
tty.c_cflag |= CLOCAL | CREAD; /* ignore modem control lines */
|
|
if (cfsetspeed(&tty, B9600) == -1) {
|
|
perror("cfsetspeed");
|
|
assert(0);
|
|
}
|
|
tty.c_iflag = IGNBRK; /* ignore BREAK condition on input */
|
|
tty.c_lflag = 0;
|
|
tty.c_oflag = 0;
|
|
tty.c_cc[VMIN] = 1; /* all reads return at least one character */
|
|
if (tcsetattr(fd, TCSANOW, &tty) == -1) {
|
|
perror("tcsetattr");
|
|
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)
|
|
{
|
|
if (left.tv_usec < right.tv_usec) {
|
|
left.tv_sec -= 1;
|
|
left.tv_usec += 1000000;
|
|
}
|
|
left.tv_sec -= right.tv_sec;
|
|
left.tv_usec -= right.tv_usec;
|
|
return left;
|
|
}
|
|
|
|
static bool
|
|
operator<(struct timeval &left, const struct timeval &right)
|
|
{
|
|
if (left.tv_sec < right.tv_sec)
|
|
return true;
|
|
if (left.tv_sec > right.tv_sec)
|
|
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;
|
|
if (gettimeofday(&start, NULL) < 0)
|
|
assert(false);
|
|
struct timeval stop = start;
|
|
stop.tv_sec += 5;
|
|
size_t count = 0;
|
|
while (start < stop) {
|
|
fd_set fdset;
|
|
FD_ZERO(&fdset);
|
|
FD_SET(fd, &fdset);
|
|
struct timeval remaining = stop;
|
|
remaining -= start;
|
|
int n = select(fd + 1, &fdset, NULL, NULL, &remaining);
|
|
if (n == 0)
|
|
break;
|
|
if (n < 0) {
|
|
err = QString("select: ") + strerror(errno);
|
|
return -1;
|
|
}
|
|
assert(nbyte > count);
|
|
int result = ::read(fd, ((char*) buf) + count, nbyte - count);
|
|
if (result < 0)
|
|
err = QString("read: ") + strerror(errno);
|
|
assert(result > 0); // else why did select return > 0?
|
|
count += result;
|
|
assert(count <= nbyte);
|
|
if (count == nbyte)
|
|
break;
|
|
if (gettimeofday(&start, NULL) < 0)
|
|
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);
|
|
FD_SET(fd, &fdset);
|
|
struct timeval tv;
|
|
memset(&tv, 0, sizeof(tv));
|
|
tv.tv_sec = 5;
|
|
int n = select(fd + 1, NULL, &fdset, NULL, &tv);
|
|
if (n < 0) {
|
|
err = QString("select: ") + strerror(errno);
|
|
return -1;
|
|
}
|
|
if (n == 0) {
|
|
err = "timeout";
|
|
return 0;
|
|
}
|
|
int result = ::write(fd, buf, nbyte);
|
|
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
|
|
Serial::name() const
|
|
{
|
|
return path;
|
|
}
|
|
|
|
bool
|
|
Serial::setBaudRate(int speed, QString &err)
|
|
{
|
|
/* not yet implemented
|
|
|
|
struct termios tty;
|
|
if (cfsetspeed(&tty, speed) == -1) {
|
|
perror("cfsetspeed");
|
|
assert(0);
|
|
}
|
|
*/
|
|
}
|
|
|
|
#ifndef Q_OS_WIN32
|
|
//
|
|
// Linux and Mac device enumerator matches wildcards in /dev
|
|
//
|
|
static int
|
|
find_devices(char *result[], int capacity)
|
|
{
|
|
regex_t reg;
|
|
DIR *dirp;
|
|
struct dirent *dp;
|
|
int count = 0;
|
|
|
|
// updated serial regexp to include many more serial devices, regardless of whether they are
|
|
// relevant for PT downloads. The original list was rather restrictive in this respect
|
|
//
|
|
// To help decode this regexp;
|
|
// /dev/cu.PL2303-[0-9A-F]+ - Prolific device driver for USB/serial device
|
|
// /dev/ANTUSBStick.slabvcp - Silicon Labs Virtual Com driver for Garmin USB1 stick on a Mac
|
|
// /dev/SLAB_USBtoUART - Silicon Labs Driver for USB/Serial
|
|
// /dev/usbmodem[0-9A-F]+ - Usb modem module driver (generic)
|
|
// /dev/usbserial-[0-9A-F]+ - usbserial module driver (generic)
|
|
// /dev/KeySerial[0-9] - Keyspan USB/Serial driver
|
|
// /dev/ttyU[0-9] - Open BSD usb serial devices
|
|
// /dev/ttyUSB[0-9] - Standard USB/Serial device on Linux/Mac
|
|
// /dev/ttyS[0-2] - Serial TTY, 0-2 is restrictive, but noone has complained yet!
|
|
// /dev/ttyACM* - ACM converter, admittidly used largely for Mobiles
|
|
// /dev/ttyMI* - MOXA PCI cards
|
|
// /dev/rfcomm* - Bluetooth devices
|
|
if (regcomp(®,
|
|
"^(cu\\.(PL2303-[0-9A-F]+|ANTUSBStick.slabvcp|SLAB_USBtoUART|usbmodem[0-9A-F]+|usbserial-[0-9A-G]+|KeySerial[0-9])|ttyU[0-9]|ttyUSB[0-9]|ttyS[0-2]|ttyACM*|ttyMI*|rfcomm*)$",
|
|
REG_EXTENDED|REG_NOSUB)) {
|
|
assert(0);
|
|
}
|
|
dirp = opendir("/dev");
|
|
while ((count < capacity) && ((dp = readdir(dirp)) != NULL)) {
|
|
if (regexec(®, dp->d_name, 0, NULL, 0) == 0) {
|
|
result[count] = (char*) malloc(6 + strlen(dp->d_name));
|
|
sprintf(result[count], "/dev/%s", dp->d_name);
|
|
++count;
|
|
}
|
|
}
|
|
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<CommPortPtr>
|
|
Serial::myListCommPorts(QString &err)
|
|
{
|
|
static const int MAX_DEVICES = 100;
|
|
(void) err;
|
|
QVector<CommPortPtr> result;
|
|
char *devices[MAX_DEVICES];
|
|
int devcnt = find_devices(devices, MAX_DEVICES);
|
|
if (devcnt == 0)
|
|
err = "No serial devices found.";
|
|
for (int i = 0; i < devcnt; ++i) {
|
|
result.append(CommPortPtr(new Serial(devices[i])));
|
|
free(devices[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|