mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
Support Garmin USB2 sticks on Linux
Fixup support for USB2 on Linux via libusb. The code is pretty ghetto with a maze of pre-processor conditionals. But it works. Of special note: * You MUST build with a very specific version of libusb, the stock libs in Ubuntu do not work. Might pull the code into the main repo, since its pretty darned tiny. * On Linux access controls for the USB devices is controlled by udev, and requires a rule adding to ensure the devie can be read/written by users other than root. A udev rule file has been added to a 'linux' subdirectory in src. Some of the timeouts and usb setup/reset code has been adjusted to ensure the device is always in a consistent state. This appears to be more important on Linux than Windows for some reason.
This commit is contained in:
36
src/ANT.cpp
36
src/ANT.cpp
@@ -104,9 +104,9 @@ ANT::ANT(QObject *parent, DeviceConfiguration *devConf) : QThread(parent)
|
||||
connect(antChannel[i], SIGNAL(searchComplete(int)), this, SLOT(searchComplete(int)));
|
||||
}
|
||||
|
||||
// on windows we use libusb to read from USB2
|
||||
// on windows and linux we use libusb to read from USB2
|
||||
// sticks, if it is not available we use stubs
|
||||
#ifdef WIN32
|
||||
#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__)
|
||||
usbMode = USBNone;
|
||||
usb2 = new LibUsb();
|
||||
#endif
|
||||
@@ -114,7 +114,7 @@ ANT::ANT(QObject *parent, DeviceConfiguration *devConf) : QThread(parent)
|
||||
|
||||
ANT::~ANT()
|
||||
{
|
||||
#ifdef WIN32
|
||||
#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__)
|
||||
delete usb2;
|
||||
#endif
|
||||
}
|
||||
@@ -183,7 +183,7 @@ void ANT::run()
|
||||
// read more bytes from the device
|
||||
uint8_t byte;
|
||||
if (rawRead(&byte, 1) > 0) receiveByte((unsigned char)byte);
|
||||
else msleep(1);
|
||||
else msleep(5);
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// LISTEN TO CONTROLLER FOR COMMANDS
|
||||
@@ -646,6 +646,13 @@ int ANT::closePort()
|
||||
break;
|
||||
}
|
||||
#else
|
||||
|
||||
#if defined __linux__ && defined GC_HAVE_LIBUSB
|
||||
if (usbMode == USB2) {
|
||||
usb2->close();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
tcflush(devicePort, TCIOFLUSH); // clear out the garbage
|
||||
return close(devicePort);
|
||||
#endif
|
||||
@@ -677,6 +684,15 @@ int ANT::openPort()
|
||||
int ldisc=N_TTY; // LINUX
|
||||
#endif
|
||||
|
||||
#if defined __linux__&& defined GC_HAVE_LIBUSB
|
||||
int rc;
|
||||
if ((rc=usb2->open()) != -1) {
|
||||
usbMode = USB2;
|
||||
return rc;
|
||||
} else {
|
||||
usbMode = USB1;
|
||||
}
|
||||
#endif
|
||||
if ((devicePort=open(deviceFilename.toAscii(),O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1)
|
||||
return errno;
|
||||
|
||||
@@ -736,6 +752,13 @@ int ANT::rawWrite(uint8_t *bytes, int size) // unix!!
|
||||
return rc;
|
||||
|
||||
#else
|
||||
|
||||
#if defined __linux__ && defined GC_HAVE_LIBUSB
|
||||
if (usbMode == USB2) {
|
||||
return usb2->write((char *)bytes, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
int ibytes;
|
||||
|
||||
ioctl(devicePort, FIONREAD, &ibytes);
|
||||
@@ -771,6 +794,11 @@ int ANT::rawRead(uint8_t bytes[], int size)
|
||||
|
||||
#else
|
||||
|
||||
#if defined __linux__ && defined GC_HAVE_LIBUSB
|
||||
if (usbMode == USB2) {
|
||||
return usb2->read((char *)bytes, size);
|
||||
}
|
||||
#endif
|
||||
int timeout=0, i=0;
|
||||
uint8_t byte;
|
||||
|
||||
|
||||
16
src/ANT.h
16
src/ANT.h
@@ -58,13 +58,18 @@
|
||||
#include <windows.h>
|
||||
#include <winbase.h>
|
||||
#include "USBXpress.h" // for Garmin USB1 sticks
|
||||
#include "LibUsb.h" // for Garmin USB2 sticks
|
||||
#else
|
||||
#include <termios.h> // unix!!
|
||||
#include <unistd.h> // unix!!
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__)
|
||||
#include "LibUsb.h" // for Garmin USB2 sticks
|
||||
#endif
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
// timeouts for read/write of serial port in ms
|
||||
#define ANT_READTIMEOUT 1000
|
||||
#define ANT_WRITETIMEOUT 2000
|
||||
@@ -324,14 +329,17 @@ private:
|
||||
#ifdef WIN32
|
||||
HANDLE devicePort; // file descriptor for reading from com3
|
||||
DCB deviceSettings; // serial port settings baud rate et al
|
||||
LibUsb *usb2; // used for USB2 support
|
||||
enum UsbMode { USBNone, USB1, USB2 };
|
||||
enum UsbMode usbMode;
|
||||
#else
|
||||
int devicePort; // unix!!
|
||||
struct termios deviceSettings; // unix!!
|
||||
#endif
|
||||
|
||||
#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__)
|
||||
LibUsb *usb2; // used for USB2 support
|
||||
enum UsbMode { USBNone, USB1, USB2 };
|
||||
enum UsbMode usbMode;
|
||||
#endif
|
||||
|
||||
// telemetry and state
|
||||
QStringList antIDs;
|
||||
#if 0
|
||||
|
||||
@@ -275,7 +275,6 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message)
|
||||
checkCinqo();
|
||||
|
||||
} else {
|
||||
|
||||
//
|
||||
// We got some telemetry on this channel
|
||||
//
|
||||
|
||||
141
src/LibUsb.cpp
141
src/LibUsb.cpp
@@ -16,17 +16,16 @@
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifdef WIN32
|
||||
#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__)
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
|
||||
#include "LibUsb.h"
|
||||
|
||||
#ifdef GC_HAVE_LIBUSB // only include if windows and have libusb installed
|
||||
|
||||
|
||||
LibUsb::LibUsb()
|
||||
{
|
||||
// dynamix load of libusb on Windows, it is statically linked in Linux
|
||||
#ifdef WIN32
|
||||
QLibrary libusb0("libusb0");
|
||||
|
||||
/*************************************************************************
|
||||
@@ -54,14 +53,16 @@ LibUsb::LibUsb()
|
||||
return;
|
||||
}
|
||||
/************************************************************************/
|
||||
#endif
|
||||
|
||||
|
||||
intf = NULL;
|
||||
readBufIndex = 0;
|
||||
readBufSize = 0;
|
||||
|
||||
usb_set_debug(255);
|
||||
usb_set_debug(0);
|
||||
|
||||
#ifdef WIN32
|
||||
// Initialize the library.
|
||||
usb_init();
|
||||
|
||||
@@ -70,26 +71,41 @@ LibUsb::LibUsb()
|
||||
|
||||
// Find all connected devices.
|
||||
usb_find_devices();
|
||||
#endif
|
||||
}
|
||||
|
||||
int LibUsb::open()
|
||||
{
|
||||
if (!isDllLoaded)
|
||||
return -1;
|
||||
#ifdef WIN32
|
||||
if (!isDllLoaded) return -1;
|
||||
#else
|
||||
// Initialize the library.
|
||||
usb_init();
|
||||
|
||||
// Search busses & devices for USB2 ANT+ stick
|
||||
device = OpenAntStick();
|
||||
// Find all busses.
|
||||
usb_find_busses();
|
||||
|
||||
if (device == NULL)
|
||||
return -1;
|
||||
// Find all connected devices.
|
||||
usb_find_devices();
|
||||
#endif
|
||||
|
||||
int rc = usb_clear_halt(device, writeEndpoint);
|
||||
if (rc < 0)
|
||||
qDebug()<<"usb_clear_halt writeEndpoint Error: "<< usb_strerror();
|
||||
readBufSize = 0;
|
||||
readBufIndex = 0;
|
||||
|
||||
// Search USB busses for USB2 ANT+ stick host controllers
|
||||
device = OpenAntStick();
|
||||
|
||||
if (device == NULL) return -1;
|
||||
|
||||
// reset the device, god only knows what mess we left it in...
|
||||
int rc = usb_reset(device);
|
||||
if (rc < 0) qDebug()<<"usb_reset Error: "<< usb_strerror();
|
||||
|
||||
rc = usb_clear_halt(device, writeEndpoint);
|
||||
if (rc < 0) qDebug()<<"usb_clear_halt writeEndpoint Error: "<< usb_strerror();
|
||||
|
||||
rc = usb_clear_halt(device, readEndpoint);
|
||||
if (rc < 0)
|
||||
qDebug()<<"usb_clear_halt readEndpoint Error: "<< usb_strerror();
|
||||
if (rc < 0) qDebug()<<"usb_clear_halt readEndpoint Error: "<< usb_strerror();
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -98,25 +114,32 @@ void LibUsb::close()
|
||||
{
|
||||
|
||||
if (device) {
|
||||
usb_release_interface(device, 0);
|
||||
usb_close(device);
|
||||
}
|
||||
// stop any further write attempts whilst we close down
|
||||
usb_dev_handle *p = device;
|
||||
device = NULL;
|
||||
|
||||
device = NULL;
|
||||
usb_release_interface(p, interface);
|
||||
usb_close(p);
|
||||
}
|
||||
}
|
||||
|
||||
int LibUsb::read(char *buf, int bytes)
|
||||
{
|
||||
if (!isDllLoaded)
|
||||
return -1;
|
||||
// check it isn't closed
|
||||
if (!device) return -1;
|
||||
|
||||
#ifdef WIN32
|
||||
if (!isDllLoaded) return -1;
|
||||
#endif
|
||||
|
||||
// The USB2 stick really doesn't like you reading 1 byte when more are available
|
||||
// so we need a buffered reader
|
||||
// so we need a buffered reader BUT ON WINDOWS ONLY appears to be a driver issue
|
||||
// and not related to the underlying hardware
|
||||
|
||||
int bufRemain = readBufSize - readBufIndex;
|
||||
|
||||
// Can we entirely satisfy the request from the buffer?
|
||||
if (bufRemain > bytes)
|
||||
if (bufRemain >= bytes)
|
||||
{
|
||||
// Yes, so do it
|
||||
memcpy(buf, readBuf+readBufIndex, bytes);
|
||||
@@ -125,18 +148,20 @@ int LibUsb::read(char *buf, int bytes)
|
||||
}
|
||||
|
||||
// No, so partially satisfy by emptying the buffer, then refill the buffer for the rest
|
||||
memcpy(buf, readBuf+readBufIndex, bufRemain);
|
||||
if (bufRemain) {
|
||||
memcpy(buf, readBuf+readBufIndex, bufRemain);
|
||||
}
|
||||
|
||||
readBufSize = 0;
|
||||
readBufIndex = 0;
|
||||
|
||||
int rc = usb_bulk_read(device, readEndpoint, readBuf, 64, 1000);
|
||||
|
||||
int rc = usb_bulk_read(device, readEndpoint, readBuf, 64, 125);
|
||||
if (rc < 0)
|
||||
{
|
||||
qDebug()<<"usb_bulk_read Error reading: "<< usb_strerror();
|
||||
// don't report timeouts - lots of noise so commented out
|
||||
//qDebug()<<"usb_bulk_read Error reading: "<<rc<< usb_strerror();
|
||||
return rc;
|
||||
}
|
||||
|
||||
readBufSize = rc;
|
||||
|
||||
int bytesToGo = bytes - bufRemain;
|
||||
@@ -159,14 +184,26 @@ int LibUsb::read(char *buf, int bytes)
|
||||
|
||||
int LibUsb::write(char *buf, int bytes)
|
||||
{
|
||||
if (!isDllLoaded)
|
||||
return -1;
|
||||
#ifdef WIN32
|
||||
if (!isDllLoaded) return -1;
|
||||
#endif
|
||||
|
||||
// check it isn't closed
|
||||
if (!device) return -1;
|
||||
|
||||
#ifdef WIN32
|
||||
int rc = usb_interrupt_write(device, writeEndpoint, buf, bytes, 1000);
|
||||
#else
|
||||
//int rc=0;
|
||||
//for (int i=0;rc >= 0 && i<bytes;i ++) // interrupt writes in pairs
|
||||
//usb_interrupt_write(device, writeEndpoint, buf+i, 1, 1000);
|
||||
int rc = usb_bulk_write(device, writeEndpoint, buf, bytes, 125);
|
||||
#endif
|
||||
|
||||
if (rc < 0)
|
||||
{
|
||||
qDebug()<<"usb_interrupt_write Error writing: "<< usb_strerror();
|
||||
// don't report timeouts - lots of noise
|
||||
if (rc != -110) qDebug()<<"usb_interrupt_write Error writing: "<< usb_strerror();
|
||||
}
|
||||
|
||||
return rc;
|
||||
@@ -184,7 +221,7 @@ struct usb_dev_handle* LibUsb::OpenAntStick()
|
||||
{
|
||||
if (dev->descriptor.idVendor == GARMIN_USB2_VID && dev->descriptor.idProduct == GARMIN_USB2_PID)
|
||||
{
|
||||
qDebug() << "Found a Garmin USB2 ANT+ stick";
|
||||
//qDebug() << "Found a Garmin USB2 ANT+ stick";
|
||||
|
||||
if ((udev = usb_open(dev)))
|
||||
{
|
||||
@@ -193,14 +230,18 @@ struct usb_dev_handle* LibUsb::OpenAntStick()
|
||||
if ((intf = usb_find_interface(&dev->config[0])) != NULL)
|
||||
{
|
||||
int rc = usb_set_configuration(udev, 1);
|
||||
if (rc < 0)
|
||||
if (rc < 0) {
|
||||
qDebug()<<"usb_set_configuration Error: "<< usb_strerror();
|
||||
rc = usb_claim_interface(udev, 0);
|
||||
if (rc < 0)
|
||||
qDebug()<<"usb_claim_interface Error: "<< usb_strerror();
|
||||
rc = usb_set_altinterface(udev, 0);
|
||||
if (rc < 0)
|
||||
qDebug()<<"usb_set_altinterface Error: "<< usb_strerror();
|
||||
#ifdef __linux__
|
||||
qDebug()<<"check permissions on:"<<QString("/dev/bus/usb/%1/%2").arg(bus->dirname).arg(dev->filename);
|
||||
#endif
|
||||
}
|
||||
rc = usb_claim_interface(udev, interface);
|
||||
if (rc < 0) qDebug()<<"usb_claim_interface Error: "<< usb_strerror();
|
||||
|
||||
rc = usb_set_altinterface(udev, alternate);
|
||||
if (rc < 0) qDebug()<<"usb_set_altinterface Error: "<< usb_strerror();
|
||||
|
||||
return udev;
|
||||
}
|
||||
}
|
||||
@@ -219,20 +260,21 @@ struct usb_interface_descriptor* LibUsb::usb_find_interface(struct usb_config_de
|
||||
|
||||
readEndpoint = -1;
|
||||
writeEndpoint = -1;
|
||||
interface = -1;
|
||||
alternate = -1;
|
||||
|
||||
if (!config_descriptor)
|
||||
return NULL;
|
||||
if (!config_descriptor) return NULL;
|
||||
|
||||
if (!config_descriptor->bNumInterfaces)
|
||||
return NULL;
|
||||
if (!config_descriptor->bNumInterfaces) return NULL;
|
||||
|
||||
if (!config_descriptor->interface[0].num_altsetting)
|
||||
return NULL;
|
||||
if (!config_descriptor->interface[0].num_altsetting) return NULL;
|
||||
|
||||
intf = &config_descriptor->interface[0].altsetting[0];
|
||||
|
||||
if (intf->bNumEndpoints != 2)
|
||||
return NULL;
|
||||
if (intf->bNumEndpoints != 2) return NULL;
|
||||
|
||||
interface = intf->bInterfaceNumber;
|
||||
alternate = intf->bAlternateSetting;
|
||||
|
||||
for (int i = 0 ; i < 2; i++)
|
||||
{
|
||||
@@ -271,5 +313,4 @@ int LibUsb::write(char *, int)
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif // Have LIBUSB
|
||||
#endif // WIN32
|
||||
#endif // Have LIBUSB and WIN32 or LINUX
|
||||
|
||||
12
src/LibUsb.h
12
src/LibUsb.h
@@ -19,10 +19,11 @@
|
||||
#ifndef gc_LibUsb_h
|
||||
#define gc_LibUsb_h
|
||||
|
||||
#if defined WIN32
|
||||
#if defined GC_HAVE_LIBUSB && (defined WIN32 || defined __linux__)
|
||||
|
||||
#ifdef GC_HAVE_LIBUSB
|
||||
#include <usb.h> // for the constants etc
|
||||
|
||||
#ifdef WIN32
|
||||
#include <QLibrary> // for dynamically loading libusb0.dll
|
||||
#endif
|
||||
|
||||
@@ -38,8 +39,8 @@ public:
|
||||
int read(char *buf, int bytes);
|
||||
int write(char *buf, int bytes);
|
||||
private:
|
||||
#ifdef GC_HAVE_LIBUSB
|
||||
|
||||
#ifdef WIN32 // we only do dynamic loading on Windows
|
||||
/*************************************************************************
|
||||
* Functions to load from libusb0.dll
|
||||
*/
|
||||
@@ -70,12 +71,16 @@ private:
|
||||
IntUsb_dev_handleIntProto usb_claim_interface;
|
||||
IntUsb_dev_handleIntProto usb_set_altinterface;
|
||||
/************************************************************************/
|
||||
#endif
|
||||
|
||||
struct usb_dev_handle* OpenAntStick();
|
||||
struct usb_interface_descriptor* usb_find_interface(struct usb_config_descriptor* config_descriptor);
|
||||
struct usb_dev_handle* device;
|
||||
struct usb_interface_descriptor* intf;
|
||||
|
||||
int readEndpoint, writeEndpoint;
|
||||
int interface;
|
||||
int alternate;
|
||||
|
||||
char readBuf[64];
|
||||
int readBufIndex;
|
||||
@@ -84,5 +89,4 @@ private:
|
||||
bool isDllLoaded;
|
||||
#endif
|
||||
};
|
||||
#endif // WIN32
|
||||
#endif // gc_LibUsb_h
|
||||
|
||||
@@ -90,6 +90,8 @@ D2XX_INCLUDE = /usr/local/include/D2XX
|
||||
# ** Garmin USB2 sticks whose driver uses libusb-win32.
|
||||
# You must make sure you install v0.1.12.2 of libusb-win from:
|
||||
# http://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/0.1.12.2/
|
||||
# or on Linux you MUST use 1.12.2 from:
|
||||
# http://prdownloads.sourceforge.net/libusb/libusb-0.1.12.tar.gz
|
||||
#LIBUSB_INSTALL=/c/libusb-win32-device-bin-0.1.12.2
|
||||
|
||||
# if you want video playback on training mode then
|
||||
|
||||
2
src/linux/51-garmin-usb.rules
Normal file
2
src/linux/51-garmin-usb.rules
Normal file
@@ -0,0 +1,2 @@
|
||||
ATTRS{idVendor}=="0fcf", ATTRS{idProduct}=="1008", MODE="0666"
|
||||
|
||||
16
src/linux/README
Normal file
16
src/linux/README
Normal file
@@ -0,0 +1,16 @@
|
||||
LINUX ACCESS PERMISSIONS FOR GARMIN USB2 STICK
|
||||
|
||||
When using Garmin USB2 sticks on Linux the udev rules that
|
||||
control access to the USB devices tend to mount the Garmin
|
||||
stick with permissions that restrict access to normal users.
|
||||
|
||||
To change the way permissions are granted you need to configure
|
||||
udev with a rules file.
|
||||
|
||||
On UBUNTU and other Debian based distros this is achieved by
|
||||
writing a rules file and placing it in /etc/udev/rules.d.
|
||||
|
||||
We have createed that file (51-garmin-usb.rules) here and
|
||||
you will need to copy it into /dev/udev/rules.d for the
|
||||
Garmin stick to be accessable from GoldenCheetah when run
|
||||
as a normal (not root) user.
|
||||
16
src/src.pro
16
src/src.pro
@@ -83,10 +83,20 @@ qwt3d {
|
||||
DEFINES += GC_HAVE_USBXPRESS
|
||||
}
|
||||
|
||||
# are we supporting USB2 devices on Windows?
|
||||
# are we supporting USB2 devices
|
||||
!isEmpty( LIBUSB_INSTALL ) {
|
||||
INCLUDEPATH += $${LIBUSB_INSTALL}/include
|
||||
DEFINES += GC_HAVE_LIBUSB
|
||||
SOURCES += LibUsb.cpp
|
||||
HEADERS += LibUsb.h
|
||||
|
||||
macx {
|
||||
// not supported at present
|
||||
} else {
|
||||
unix {
|
||||
LIBS += $${LIBUSB_INSTALL}/lib/libusb.a
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# are we supporting video playback?
|
||||
@@ -123,8 +133,8 @@ win32 {
|
||||
RC_FILE = windowsico.rc
|
||||
|
||||
# Windows only USB support
|
||||
SOURCES += USBXpress.cpp LibUsb.cpp
|
||||
HEADERS += USBXpress.h LibUsb.h
|
||||
SOURCES += USBXpress.cpp
|
||||
HEADERS += USBXpress.h
|
||||
}
|
||||
|
||||
# local qxt widgets - rather than add another dependency on libqxt
|
||||
|
||||
Reference in New Issue
Block a user