From f8d60bfbe28108b0dae5f845779888c5df56f90d Mon Sep 17 00:00:00 2001 From: Darren Hague Date: Sat, 19 Mar 2011 20:50:42 +0000 Subject: [PATCH] Native ANT+ part 2 - USB2 Support and minor improvements This patch adds support for the Garmin USB2 stick using libusb-win library. Instructions are included in gcconfig.pri.in for configuring and installing the neccessary libs. To enable support for USB1 and USB2 support in the same binary stubs are created when UsbXpress/Libusb are not available and the device i/o attempts to use USB2 before falling back to USB1. Since I was also in the middle of some coding changes I merged my developments (Mark) with Darren's patch whilst fixing it up for commit, namely: 1. the configuration screen no longer demands a COMx port when using Native ANT+ on Windows. 2. new signals in ANTChannel notify the ANT class when info is stale or lost (but they are not used at present). 3. The previous debug messages have been removed, although new debug messages are added for stale/drop/timeout signals. --- src/ANT.cpp | 189 ++++++++++++++++++++--------------- src/ANT.h | 13 ++- src/ANTChannel.cpp | 45 ++++----- src/ANTChannel.h | 75 +++++++------- src/ANTMessage.cpp | 38 +------ src/ANTMessage.h | 3 + src/DeviceTypes.cpp | 8 +- src/DeviceTypes.h | 1 + src/LibUsb.cpp | 236 ++++++++++++++++++++++++++++++++++++++++++++ src/LibUsb.h | 53 ++++++++++ src/Pages.cpp | 53 ++++++---- src/USBXpress.cpp | 38 ++++++- src/gcconfig.pri.in | 6 ++ src/src.pro | 14 ++- 14 files changed, 563 insertions(+), 209 deletions(-) create mode 100644 src/LibUsb.cpp create mode 100644 src/LibUsb.h diff --git a/src/ANT.cpp b/src/ANT.cpp index 973c90475..840b5c0cc 100644 --- a/src/ANT.cpp +++ b/src/ANT.cpp @@ -90,11 +90,32 @@ ANT::ANT(QObject *parent, DeviceConfiguration *devConf) : QThread(parent) antIDs.clear(); // setup the channels - for (int i=0; ichannel_type & 0xf ) == device_type) && (antChannel[i]->device_number == device_number)) { // send the channel found... - antChannel[i]->channelInfo(); + //XXX antChannel[i]->channelInfo(); return 1; } } @@ -375,7 +394,8 @@ void ANT::report() { for (int i=0; ichannelInfo(); + //XXX antChannel[i]->channelInfo(); + ; } void @@ -440,6 +460,36 @@ ANT::discover(DeviceConfiguration *, QProgressDialog *) return false; } +void +ANT::dropInfo(int number) // we dropped a connection +{ + qDebug()<<"drop info for channel"<= 0 && channel < 4) { - // handle a channel event here! -//qDebug()<<"channel event on channel:"<receiveMessage(rxMessage); } } @@ -519,7 +567,6 @@ ANT::handleChannelEvent(void) { void ANT::processMessage(void) { -//qDebug()<<"processing ant message"<close(); + return 0; + break; + case USB1 : + return (int)!CloseHandle(devicePort); + break; + default : + return -1; + break; + } #else tcflush(devicePort, TCIOFLUSH); // clear out the garbage return close(devicePort); @@ -578,8 +636,22 @@ int ANT::closePort() int ANT::openPort() { -#ifndef WIN32 +#ifdef WIN32 + int rc; + // on windows we try on USB2 then on USB1 then fail... + if ((rc=usb2->open()) != -1) { + usbMode = USB2; + return rc; + } else if ((rc= USBXpress::open(&devicePort)) != -1) { + usbMode = USB1; + return rc; + } else { + usbMode = USBNone; + return -1; + } + +#else // LINUX AND MAC USES TERMIO / IOCTL / STDIO #if defined(Q_OS_MACX) @@ -620,59 +692,6 @@ int ANT::openPort() if(tcsetattr(devicePort, TCSANOW, &deviceSettings) == -1) return errno; tcgetattr(devicePort, &deviceSettings); -#else -#ifdef GC_HAVE_USBXPRESS - return USBXpress::open(&devicePort); -#else - // WINDOWS USES SET/GETCOMMSTATE AND READ/WRITEFILE - - COMMTIMEOUTS timeouts; // timeout settings on serial ports - - // if deviceFilename references a port above COM9 - // then we need to open "\\.\COMX" not "COMX" - QString portSpec; - int portnum = deviceFilename.midRef(3).toString().toInt(); - if (portnum < 10) - portSpec = deviceFilename; - else - portSpec = "\\\\.\\" + deviceFilename; - 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 - devicePort = CreateFile (deviceFilenameW, GENERIC_READ|GENERIC_WRITE, - FILE_SHARE_DELETE|FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - - if (devicePort == INVALID_HANDLE_VALUE) return -1; - - if (GetCommState (devicePort, &deviceSettings) == false) return -1; - - // so we've opened the comm port lets set it up for - deviceSettings.BaudRate = CBR_2400; - deviceSettings.fParity = NOPARITY; - deviceSettings.ByteSize = 8; - deviceSettings.StopBits = ONESTOPBIT; - deviceSettings.EofChar = 0x0; - deviceSettings.ErrorChar = 0x0; - deviceSettings.EvtChar = 0x0; - deviceSettings.fBinary = true; - deviceSettings.fRtsControl = RTS_CONTROL_HANDSHAKE; - deviceSettings.fOutxCtsFlow = TRUE; - - - if (SetCommState(devicePort, &deviceSettings) == false) { - CloseHandle(devicePort); - return -1; - } - - timeouts.ReadIntervalTimeout = 0; - timeouts.ReadTotalTimeoutConstant = 1000; - timeouts.ReadTotalTimeoutMultiplier = 50; - timeouts.WriteTotalTimeoutConstant = 2000; - timeouts.WriteTotalTimeoutMultiplier = 0; - SetCommTimeouts(devicePort, &timeouts); -#endif #endif // success @@ -684,13 +703,19 @@ int ANT::rawWrite(uint8_t *bytes, int size) // unix!! int rc=0; #ifdef WIN32 -#ifdef GC_HAVE_USBXPRESS - rc = USBXpress::write(&devicePort, bytes, size); -#else - DWORD cBytes; - rc = WriteFile(devicePort, bytes, size, &cBytes, NULL); -#endif - if (!rc) return -1; + switch (usbMode) { + case USB1: + rc = USBXpress::write(&devicePort, bytes, size); + break; + case USB2: + rc = usb2->write((char *)bytes, size); + break; + default: + rc = 0; + break; + } + + if (!rc) rc = -1; // return -1 if nothing written return rc; #else @@ -704,9 +729,9 @@ int ANT::rawWrite(uint8_t *bytes, int size) // unix!! if (rc != -1) tcdrain(devicePort); // wait till its gone. ioctl(devicePort, FIONREAD, &ibytes); + return rc; #endif - return rc; } @@ -715,16 +740,18 @@ int ANT::rawRead(uint8_t bytes[], int size) int rc=0; #ifdef WIN32 -#ifdef GC_HAVE_USBXPRESS - return USBXpress::read(&devicePort, bytes, size); -#else + switch (usbMode) { + case USB1: + return USBXpress::read(&devicePort, bytes, size); + break; + case USB2: + return usb2->read((char *)bytes, size); + break; + default: + rc = 0; + break; + } - // Readfile deals with timeouts and readyread issues - DWORD cBytes; - rc = ReadFile(devicePort, bytes, size, &cBytes, NULL); - if (rc) return (int)cBytes; - else return (-1); -#endif #else int timeout=0, i=0; diff --git a/src/ANT.h b/src/ANT.h index 2fbee61ef..0ea231e6a 100644 --- a/src/ANT.h +++ b/src/ANT.h @@ -58,6 +58,7 @@ #include #include #include "USBXpress.h" // for Garmin USB1 sticks +#include "LibUsb.h" // for Garmin USB2 sticks #else #include // unix!! #include // unix!! @@ -240,6 +241,11 @@ public slots: // channel management bool discover(DeviceConfiguration *, QProgressDialog *); // confirm Server available at portSpec + void dropInfo(int number); // we dropped a connection + void lostInfo(int number); // we lost informa + void staleInfo(int number); // info is now stale + void searchTimeout(int number); // search timed out + void searchComplete(int number); // search completed successfully // get telemetry RealtimeData getRealtimeData(); // return current realtime data @@ -310,6 +316,9 @@ 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!! @@ -324,8 +333,8 @@ private: unsigned char rxMessage[ANT_MAX_MESSAGE_SIZE]; // state machine whilst receiving bytes - enum States {ST_WAIT_FOR_SYNC, ST_GET_LENGTH, ST_GET_MESSAGE_ID, ST_GET_DATA, ST_VALIDATE_PACKET}; - enum States state; + enum States {ST_WAIT_FOR_SYNC, ST_GET_LENGTH, ST_GET_MESSAGE_ID, ST_GET_DATA, ST_VALIDATE_PACKET} state; + //enum States state; int length; int bytes; int checksum; diff --git a/src/ANTChannel.cpp b/src/ANTChannel.cpp index d58c3dd07..565208933 100755 --- a/src/ANTChannel.cpp +++ b/src/ANTChannel.cpp @@ -25,7 +25,7 @@ static float timeout_drop=2.0; // time before reporting dropped message static float timeout_scan=10.0; // time to do initial scan static float timeout_lost=30.0; // time to do more thorough scan -ANTChannel::ANTChannel(int number, ANT *parent) : number(number), parent(parent) +ANTChannel::ANTChannel(int number, ANT *parent) : parent(parent), number(number) { channel_type=CHANNEL_TYPE_UNUSED; channel_type_flags=0; @@ -97,7 +97,7 @@ void ANTChannel::open(int device, int chan_type) // close an ant channel assignment void ANTChannel::close() { - lostInfo(); + emit lostInfo(number); lastMessage = ANTMessage(); parent->sendMessage(ANTMessage::close(number)); } @@ -133,7 +133,7 @@ void ANTChannel::receiveMessage(unsigned char *ant_message) if (get_timestamp() > blanking_timestamp + timeout_blanking) { if (!blanked) { blanked=1; - staleInfo(); //XXX does nothing for now.... + emit staleInfo(number); } } else blanked=0; } @@ -159,12 +159,14 @@ void ANTChannel::channelEvent(unsigned char *ant_message) { // timeouts are normal for search channel if (channel_type_flags & CHANNEL_TYPE_QUICK_SEARCH) { - channel_type_flags &= ~CHANNEL_TYPE_QUICK_SEARCH; - channel_type_flags |= CHANNEL_TYPE_WAITING; + channel_type_flags &= ~CHANNEL_TYPE_QUICK_SEARCH; + channel_type_flags |= CHANNEL_TYPE_WAITING; + + emit searchTimeout(number); } else { - lostInfo(); //XXX does nothing for now + emit lostInfo(number); channel_type=CHANNEL_TYPE_UNUSED; channel_type_flags=0; @@ -182,7 +184,7 @@ void ANTChannel::channelEvent(unsigned char *ant_message) { double t=get_timestamp(); if (t > (last_message_timestamp + timeout_drop)) { - if (channel_type != CHANNEL_TYPE_UNUSED) dropInfo(); + if (channel_type != CHANNEL_TYPE_UNUSED) emit dropInfo(number); // this is a hacky way to prevent the drop message from sending multiple times last_message_timestamp+=2*timeout_drop; } @@ -248,7 +250,6 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) last_message_timestamp=timestamp; if (state != MESSAGE_RECEIVED) { -qDebug()<<"who are you? sent"; // first message! who are we talking to? parent->sendMessage(ANTMessage::requestMessage(number, ANT_CHANNEL_ID)); blanking_timestamp=get_timestamp(); @@ -278,7 +279,6 @@ qDebug()<<"who are you? sent"; // // We got some telemetry on this channel // -qDebug()<<"broadcast datapage="<sendMessage(ANTMessage::setSearchTimeout(number, (int)(timeout_lost/2.5))); } @@ -634,7 +633,6 @@ void ANTChannel::attemptTransition(int message_id) switch (state) { case ANT_CLOSE_CHANNEL: -//qDebug()<<"transition ... close channel..."; // next step is unassign and start over // but we must wait until event_channel_closed // which is its own channel event @@ -642,7 +640,6 @@ void ANTChannel::attemptTransition(int message_id) break; case ANT_UNASSIGN_CHANNEL: -//qDebug()<<"transition ... unassign channel..."; channel_assigned=0; if (st->type==CHANNEL_TYPE_UNUSED) { // we're shutting the channel down @@ -652,23 +649,21 @@ void ANTChannel::attemptTransition(int message_id) device_id |= 0x80; } setId(); -//qDebug()<<"st network is"<network; parent->sendMessage(ANTMessage::assignChannel(number, 0, st->network)); // recieve channel on network 1 - // XXX commented out since newer host controllers do not exhibit this issue - // XXX but may be relevant for Arduino/Sparkfun guys, but we don't really support - // XXX those devices anyway as they are too slow - // XXX parent->sendMessage(ANTMessage::boostSignal(number)); // "boost" signal on REV C AP2 devices + + // commented out since newer host controllers do not exhibit this issue + // but may be relevant for Arduino/Sparkfun guys, but we don't really support + // those devices anyway as they are too slow + // parent->sendMessage(ANTMessage::boostSignal(number)); // "boost" signal on REV C AP2 devices } break; case ANT_ASSIGN_CHANNEL: -//qDebug()<<"transition ... assign channel..."; channel_assigned=1; parent->sendMessage(ANTMessage::setChannelID(number, device_number, device_id, 0)); break; case ANT_CHANNEL_ID: -//qDebug()<<"transition ... channel id..."; if (channel_type & CHANNEL_TYPE_QUICK_SEARCH) { parent->sendMessage(ANTMessage::setSearchTimeout(number, (int)(timeout_scan/2.5))); } else { @@ -677,7 +672,6 @@ void ANTChannel::attemptTransition(int message_id) break; case ANT_SEARCH_TIMEOUT: -//qDebug()<<"transition ... search timeout..."; if (previous_state==ANT_CHANNEL_ID) { // continue down the intialization chain parent->sendMessage(ANTMessage::setChannelPeriod(number, st->period)); @@ -689,18 +683,15 @@ void ANTChannel::attemptTransition(int message_id) break; case ANT_CHANNEL_PERIOD: -//qDebug()<<"transition ... channel period..."; parent->sendMessage(ANTMessage::setChannelFreq(number, st->frequency)); break; case ANT_CHANNEL_FREQUENCY: -//qDebug()<<"transition ... channel frequency..."; parent->sendMessage(ANTMessage::open(number)); mi.initialise(); break; case ANT_OPEN_CHANNEL: -//qDebug()<<"transition ... open channel..."; break; default: @@ -711,14 +702,16 @@ void ANTChannel::attemptTransition(int message_id) // refactored out XXX fix this int ANTChannel::setTimeout(char * /*type*/, float /*value*/, int /*connection*/) { return 0; } +#if 0 // ARE NOW SIGNALS // These should emit signals to notify the channel manager // but for now we just ignore XXX fix this void ANTChannel::searchComplete() {} -void ANTChannel::reportTimeouts() {} +void ANTChannel::searchTimeout() {} void ANTChannel::staleInfo() {} void ANTChannel::lostInfo() {} void ANTChannel::dropInfo() {} void ANTChannel::channelInfo() {} +#endif // // Calibrate... XXX not used at present diff --git a/src/ANTChannel.h b/src/ANTChannel.h index e335ae397..8d6028852 100755 --- a/src/ANTChannel.h +++ b/src/ANTChannel.h @@ -22,6 +22,7 @@ #include "ANT.h" #include "ANTMessage.h" +#include #define CHANNEL_TYPE_QUICK_SEARCH 0x10 // or'ed with current channel type /* after fast search, wait for slow search. Otherwise, starting slow @@ -71,9 +72,39 @@ class ANTChannelInitialisation { }; -class ANTChannel { +class ANTChannel : public QObject { + + private: + + Q_OBJECT + + ANT *parent; + + ANTMessage lastMessage, lastStdPwrMessage; + int dualNullCount, nullCount; + double last_message_timestamp; + double blanking_timestamp; + int blanked; + char id[10]; // short identifier + bool channel_assigned; + ANTChannelInitialisation mi; + + int messages_received; // for signal strength metric + int messages_dropped; + + unsigned char rx_burst_data[RX_BURST_DATA_LEN]; + int rx_burst_data_index; + unsigned char rx_burst_next_sequence; + void (*rx_burst_disposition)(struct ant_channel *); + void (*tx_ack_disposition)(struct ant_channel *); + + // what we got + int manufacturer_id; + int product_id; + int product_version; public: + enum channeltype { CHANNEL_TYPE_UNUSED, CHANNEL_TYPE_HR, @@ -124,14 +155,6 @@ class ANTChannel { void attemptTransition(int message_code); int setTimeout(char *type, float value, int connection); - // Should be signals ? XXX - void channelInfo(); - void dropInfo(); - void lostInfo(); - void staleInfo(); - void reportTimeouts(); - void searchComplete(); - // search int isSearching(); @@ -140,33 +163,13 @@ class ANTChannel { void sendCinqoSuccess(); void checkCinqo(); - private: - - ANT *parent; - - ANTMessage lastMessage, lastStdPwrMessage; - int dualNullCount, nullCount; - double last_message_timestamp; - double blanking_timestamp; - int blanked; - char id[10]; // short identifier - bool channel_assigned; - ANTChannelInitialisation mi; - - int messages_received; // for signal strength metric - int messages_dropped; - - unsigned char rx_burst_data[RX_BURST_DATA_LEN]; - int rx_burst_data_index; - unsigned char rx_burst_next_sequence; - void (*rx_burst_disposition)(struct ant_channel *); - void (*tx_ack_disposition)(struct ant_channel *); - - - // what we got - int manufacturer_id; - int product_id; - int product_version; + signals: + void channelInfo(); // we got a channel info message + void dropInfo(int number); // we dropped a connection + void lostInfo(int number); // we lost informa + void staleInfo(int number); // info is now stale + void searchTimeout(int number); // search timed out + void searchComplete(int number); // search completed successfully }; #endif diff --git a/src/ANTMessage.cpp b/src/ANTMessage.cpp index ffb4eabbf..fa37cac22 100755 --- a/src/ANTMessage.cpp +++ b/src/ANTMessage.cpp @@ -185,9 +185,9 @@ ANTMessage::ANTMessage(void) { init(); } -// static helper to conver message codes to an english string -// when outputing diagnostics for received messages -static const char *channelEventMessage(unsigned char c) +// convert message codes to an english string +const char * +ANTMessage::channelEventMessage(unsigned char c) { switch (c) { case 0 : return "No error"; @@ -237,35 +237,29 @@ ANTMessage::ANTMessage(ANT *parent, const unsigned char *message) { switch(type) { case ANT_UNASSIGN_CHANNEL: channel = message[3]; -qDebug()<<"unassign channel"<>"<< type; break; // shouldn't get here! } } diff --git a/src/ANTMessage.h b/src/ANTMessage.h index 96400252b..3e5fed386 100755 --- a/src/ANTMessage.h +++ b/src/ANTMessage.h @@ -68,6 +68,9 @@ class ANTMessage { static ANTMessage open(const unsigned char channel); static ANTMessage close(const unsigned char channel); + // convert a channel event message id to human readable string + const char * channelEventMessage(unsigned char c); + // to avoid a myriad of tedious set/getters the data fields // are plain public members. This is unlikely to change in the // future since the whole purpose of this class is to encode diff --git a/src/DeviceTypes.cpp b/src/DeviceTypes.cpp index b83124d7d..5deac4517 100644 --- a/src/DeviceTypes.cpp +++ b/src/DeviceTypes.cpp @@ -28,11 +28,15 @@ static DeviceType SupportedDevices[] = { - { DEV_CT, DEV_SERIAL, (char *) "Computrainer", true, false }, - { DEV_ANTPLUS, DEV_QUARQ, (char *) "ANT+ via Quarqd", true, false }, +#ifdef Q_OS_WIN32 + { DEV_ANTLOCAL, DEV_USB, (char *) "Native ANT+", true, false }, +#else { DEV_ANTLOCAL, DEV_SERIAL, (char *) "Native ANT+", true, false }, +#endif + { DEV_CT, DEV_SERIAL, (char *) "Computrainer", true, false }, { DEV_GSERVER, DEV_TCP, (char *) "Golden Cheetah Server", false, false }, { DEV_NULL, DEV_TCP, (char *) "Null device (testing)", false, false }, + { DEV_ANTPLUS, DEV_QUARQ, (char *) "ANT+ via Quarqd", true, false }, // { DEV_PT, DEV_SERIAL, (char *) "Powertap Head Unit", false, true }, // { DEV_SRM, DEV_SERIAL, (char *) "SRM PowerControl V/VI", false, true }, // { DEV_GCLIENT, DEV_TCP, (char *) "Golden Cheetah Client", false, false }, diff --git a/src/DeviceTypes.h b/src/DeviceTypes.h index 70e70675e..45016615c 100644 --- a/src/DeviceTypes.h +++ b/src/DeviceTypes.h @@ -37,6 +37,7 @@ #define DEV_QUARQ 0x01 // ants use id:hostname:port #define DEV_SERIAL 0x02 // use filename COMx or /dev/cuxxxx #define DEV_TCP 0x03 // tcp port is hostname:port NOT IMPLEMENTED IN THIS RELEASE +#define DEV_USB 0x04 // use filename COMx or /dev/cuxxxx class DeviceType { diff --git a/src/LibUsb.cpp b/src/LibUsb.cpp new file mode 100644 index 000000000..499d497c8 --- /dev/null +++ b/src/LibUsb.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2011 Darren Hague & Eric Brandt + * + * 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 + */ + +#ifdef WIN32 +#include +#include + +#include "LibUsb.h" + +#ifdef GC_HAVE_LIBUSB // only include if windows and have libusb installed + + +LibUsb::LibUsb() +{ + intf = NULL; + readBufIndex = 0; + readBufSize = 0; + + usb_set_debug(255); + + // Initialize the library. + usb_init(); + + // Find all busses. + usb_find_busses(); + + // Find all connected devices. + usb_find_devices(); +} + +int LibUsb::open() +{ + // Search busses & devices for USB2 ANT+ stick + device = OpenAntStick(); + + if (device == NULL) + return -1; + + int 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(); + + return rc; +} + +void LibUsb::close() +{ + if (device) { + usb_release_interface(device, 0); + usb_close(device); + } + + device = NULL; +} + +int LibUsb::read(char *buf, int bytes) +{ + // The USB2 stick really doesn't like you reading 1 byte when more are available + // so we need a buffered reader + + int bufRemain = readBufSize - readBufIndex; + + // Can we entirely satisfy the request from the buffer? + if (bufRemain > bytes) + { + // Yes, so do it + memcpy(buf, readBuf+readBufIndex, bytes); + readBufIndex += bytes; + return bytes; + } + + // No, so partially satisfy by emptying the buffer, then refill the buffer for the rest + memcpy(buf, readBuf+readBufIndex, bufRemain); + readBufSize = 0; + readBufIndex = 0; + + int rc = usb_bulk_read(device, readEndpoint, readBuf, 64, 1000); + + if (rc < 0) + { + qDebug()<<"usb_bulk_read Error reading: "<< usb_strerror(); + return rc; + } + + readBufSize = rc; + + int bytesToGo = bytes - bufRemain; + if (bytesToGo < readBufSize) + { + // If we have enough bytes in the buffer, return them + memcpy(buf+bufRemain, readBuf, bytesToGo); + readBufIndex += bytesToGo; + rc = bytes; + } else { + // Otherwise, just return what we can + memcpy(buf+bufRemain, readBuf, readBufSize); + rc = bufRemain + readBufSize; + readBufSize = 0; + readBufIndex = 0; + } + + return rc; +} + +int LibUsb::write(char *buf, int bytes) +{ + int rc = usb_interrupt_write(device, writeEndpoint, buf, bytes, 1000); + + if (rc < 0) + { + qDebug()<<"usb_interrupt_write Error writing: "<< usb_strerror(); + } + + return rc; +} + +struct usb_dev_handle* LibUsb::OpenAntStick() +{ + struct usb_bus* bus; + struct usb_device* dev; + struct usb_dev_handle* udev; + + for (bus = usb_get_busses(); bus; bus = bus->next) + { + for (dev = bus->devices; dev; dev = dev->next) + { + if (dev->descriptor.idVendor == GARMIN_USB2_VID && dev->descriptor.idProduct == GARMIN_USB2_PID) + { + qDebug() << "Found a Garmin USB2 ANT+ stick"; + + if ((udev = usb_open(dev))) + { + if (dev->descriptor.bNumConfigurations) + { + if ((intf = usb_find_interface(&dev->config[0])) != NULL) + { + int rc = usb_set_configuration(udev, 1); + 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(); + return udev; + } + } + + usb_close(udev); + } + } + } + } + return NULL; +} + +struct usb_interface_descriptor* LibUsb::usb_find_interface(struct usb_config_descriptor* config_descriptor) +{ + struct usb_interface_descriptor* intf; + + readEndpoint = -1; + writeEndpoint = -1; + + if (!config_descriptor) + return NULL; + + if (!config_descriptor->bNumInterfaces) + return NULL; + + if (!config_descriptor->interface[0].num_altsetting) + return NULL; + + intf = &config_descriptor->interface[0].altsetting[0]; + + if (intf->bNumEndpoints != 2) + return NULL; + + for (int i = 0 ; i < 2; i++) + { + if (intf->endpoint[i].bEndpointAddress & USB_ENDPOINT_DIR_MASK) + readEndpoint = intf->endpoint[i].bEndpointAddress; + else + writeEndpoint = intf->endpoint[i].bEndpointAddress; + } + + if (readEndpoint < 0 || writeEndpoint < 0) + return NULL; + + return intf; +} +#else + +// if we don't have libusb use stubs +LibUsb::LibUsb() {} + +int LibUsb::open() +{ + return -1; +} + +void LibUsb::close() +{ +} + +int LibUsb::read(char *, int) +{ + return -1; +} + +int LibUsb::write(char *, int) +{ + return -1; +} + +#endif // Have LIBUSB +#endif // WIN32 diff --git a/src/LibUsb.h b/src/LibUsb.h new file mode 100644 index 000000000..d6820fed7 --- /dev/null +++ b/src/LibUsb.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2011 Darren Hague & Eric Brandt + * + * 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 + */ + +#ifndef gc_LibUsb_h +#define gc_LibUsb_h + +#if defined WIN32 + +#ifdef GC_HAVE_LIBUSB +#include // for the constants etc +#endif + +#define GARMIN_USB2_VID 0x0fcf +#define GARMIN_USB2_PID 0x1008 + +class LibUsb { + +public: + LibUsb(); + int open(); + void close(); + int read(char *buf, int bytes); + int write(char *buf, int bytes); +private: +#ifdef GC_HAVE_LIBUSB + 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; + + char readBuf[64]; + int readBufIndex; + int readBufSize; +#endif +}; +#endif // WIN32 +#endif // gc_LibUsb_h diff --git a/src/Pages.cpp b/src/Pages.cpp index 95e79b9ed..3c780cfd7 100644 --- a/src/Pages.cpp +++ b/src/Pages.cpp @@ -866,44 +866,55 @@ DevicePage::setConfigPane() switch (Supported.getType(typeSelector->itemData(typeSelector->currentIndex()).toInt()).connector) { case DEV_QUARQ: + specHint->show(); + specLabel->show(); + deviceSpecifier->show(); specHint->setText("hostname:port"); profHint->setText("antid 1, antid 2 ..."); profHint->show(); - pairButton->show(); profLabel->show(); deviceProfile->show(); break; + case DEV_SERIAL: #ifdef WIN32 specHint->setText("COMx"); #else specHint->setText("/dev/xxxx"); #endif - // we have ANT+ sticks on serial and we have Computrainers - // on serial, which one is it? - if (Supported.getType(typeSelector->itemData(typeSelector->currentIndex()) - .toInt()).type == DEV_ANTLOCAL) { - pairButton->show(); - profHint->setText("antid 1, antid 2 ..."); - profHint->show(); - profLabel->show(); - deviceProfile->show(); - } else { - pairButton->hide(); - profHint->hide(); - profLabel->hide(); - deviceProfile->hide(); - } - break; - case DEV_TCP: - specHint->setText("hostname:port"); - pairButton->hide(); + specHint->show(); + specLabel->show(); + deviceSpecifier->show(); profHint->hide(); profLabel->hide(); deviceProfile->hide(); break; + + case DEV_TCP: + specHint->show(); + specLabel->show(); + deviceSpecifier->show(); + specHint->setText("hostname:port"); + profHint->hide(); + profLabel->hide(); + deviceProfile->hide(); + break; + + case DEV_USB: + specHint->hide(); + specLabel->hide(); + deviceSpecifier->hide(); + profHint->setText("antid 1, antid 2 ..."); + profHint->show(); + profLabel->show(); + deviceProfile->show(); + break; } - //specHint->setTextFormat(Qt::Italic); // mmm need to read the docos + + // pair button only valid for ANT+ (Quarqd or Native) + int type = Supported.getType(typeSelector->itemData(typeSelector->currentIndex()).toInt()).type; + if (type == DEV_ANTLOCAL || type == DEV_ANTPLUS) pairButton->show(); + else pairButton->hide(); } diff --git a/src/USBXpress.cpp b/src/USBXpress.cpp index 317f65c2e..0fc0c139c 100644 --- a/src/USBXpress.cpp +++ b/src/USBXpress.cpp @@ -16,12 +16,15 @@ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#if defined (WIN32) && defined (GC_HAVE_USBXPRESS) // only include if windows and have USBXpress installed - -#include "USBXpress.h" +#ifdef WIN32 #include #include +#ifdef GC_HAVE_USBXPRESS + +// if we have usbxpress installed then use it... +#include "USBXpress.h" + USBXpress::USBXpress() {} // nothing to do - all members are static int USBXpress::open(HANDLE *handle) @@ -97,4 +100,31 @@ int USBXpress::write(HANDLE *handle, unsigned char *buf, int bytes) return -1; } -#endif +#else + +// if we don't have USBXpress installed then stubs return fail +USBXpress::USBXpress() {} // nothing to do - all members are static + +int USBXpress::open(HANDLE *) +{ + return -1; +} + +int USBXpress::close(HANDLE *) +{ + return -1; +} + + +int USBXpress::read(HANDLE *, unsigned char *, int) +{ + return -1; +} + +int USBXpress::write(HANDLE *, unsigned char *, int) +{ + return -1; +} + +#endif // USBXpress +#endif // Win32 diff --git a/src/gcconfig.pri.in b/src/gcconfig.pri.in index b2ee9e5fe..779fd0053 100644 --- a/src/gcconfig.pri.in +++ b/src/gcconfig.pri.in @@ -79,6 +79,12 @@ D2XX_INCLUDE = /usr/local/include/D2XX # pathname (SiLabs in your root directory) #USBXPRESS_INSTALL=/c/SiLabs/MCU/USBXpress/USBXpress_API/Host/ +# ** note this is also only required on windowsto work with +# ** 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/ +#LIBUSB_INSTALL=/c/libusb-win32-device-bin-0.1.12.2 + # We recommend a debug build for development, and a static build for releases. CONFIG += debug #CONFIG += static diff --git a/src/src.pro b/src/src.pro index 531e33ac0..7187fa900 100644 --- a/src/src.pro +++ b/src/src.pro @@ -76,14 +76,20 @@ qwt3d { LIBS += $${ICAL_LIBS} } +# are we supporting USB1 devices on Windows? !isEmpty( USBXPRESS_INSTALL ) { LIBS += $${USBXPRESS_INSTALL}/x86/SiUSBXp.lib INCLUDEPATH += $${USBXPRESS_INSTALL} - SOURCES += USBXpress.cpp - HEADERS += USBXpress.h DEFINES += GC_HAVE_USBXPRESS } +# are we supporting USB2 devices on Windows? +!isEmpty( LIBUSB_INSTALL ) { + LIBS += $${LIBUSB_INSTALL}/lib/msvc/libusb.lib + INCLUDEPATH += $${LIBUSB_INSTALL}/include + DEFINES += GC_HAVE_LIBUSB +} + macx { LIBS += -lobjc -framework Carbon -framework AppKit HEADERS += QtMacSegmentedButton.h @@ -101,6 +107,10 @@ win32 { -Wl,--script,win32/i386pe.x-no-rdata,--enable-auto-import //QMAKE_CXXFLAGS += -fdata-sections RC_FILE = windowsico.rc + + # Windows only USB support + SOURCES += USBXpress.cpp LibUsb.cpp + HEADERS += USBXpress.h LibUsb.h } # local qxt widgets - rather than add another dependency on libqxt