diff --git a/src/ANT.cpp b/src/ANT.cpp index 408e8607c..6d698bf3e 100644 --- a/src/ANT.cpp +++ b/src/ANT.cpp @@ -31,6 +31,11 @@ #include #include "RealtimeData.h" +#ifdef Q_OS_LINUX // to get stat /dev/xxx for major/minor +#include +#include +#include +#endif /* Control status */ #define ANT_RUNNING 0x01 @@ -41,24 +46,24 @@ const unsigned char ANT::key[8] = { 0xB9, 0xA5, 0x21, 0xFB, 0xBD, 0x72, 0xC3, 0x // supported sensor types const ant_sensor_type_t ANT::ant_sensor_types[] = { - { ANTChannel::CHANNEL_TYPE_UNUSED, 0, 0, 0, 0, "Unused", '?' }, + { ANTChannel::CHANNEL_TYPE_UNUSED, 0, 0, 0, 0, "Unused", '?', "" }, { ANTChannel::CHANNEL_TYPE_HR, ANT_SPORT_HR_PERIOD, ANT_SPORT_HR_TYPE, - ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Heartrate", 'h' }, + ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Heartrate", 'h', ":images/IconHR.png" }, { ANTChannel::CHANNEL_TYPE_POWER, ANT_SPORT_POWER_PERIOD, ANT_SPORT_POWER_TYPE, - ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Power", 'p' }, + ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Power", 'p', ":images/IconPower.png" }, { ANTChannel::CHANNEL_TYPE_SPEED, ANT_SPORT_SPEED_PERIOD, ANT_SPORT_SPEED_TYPE, - ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Speed", 's' }, + ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Speed", 's', ":images/IconSpeed.png" }, { ANTChannel::CHANNEL_TYPE_CADENCE, ANT_SPORT_CADENCE_PERIOD, ANT_SPORT_CADENCE_TYPE, - ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Cadence", 'c' }, + ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Cadence", 'c', ":images/IconCadence.png" }, { ANTChannel::CHANNEL_TYPE_SandC, ANT_SPORT_SandC_PERIOD, ANT_SPORT_SandC_TYPE, - ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Speed + Cadence", 'd' }, + ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Speed + Cadence", 'd', ":images/IconCadence.png" }, { ANTChannel::CHANNEL_TYPE_QUARQ, ANT_QUARQ_PERIOD, ANT_QUARQ_TYPE, - ANT_QUARQ_FREQUENCY, DEFAULT_NETWORK_NUMBER, "Quarq Channel", 'Q' }, + ANT_QUARQ_FREQUENCY, DEFAULT_NETWORK_NUMBER, "Quarq Channel", 'Q', ":images/IconPower.png" }, { ANTChannel::CHANNEL_TYPE_FAST_QUARQ, ANT_FAST_QUARQ_PERIOD, ANT_FAST_QUARQ_TYPE, - ANT_FAST_QUARQ_FREQUENCY, DEFAULT_NETWORK_NUMBER, "Fast Quarq", 'q' }, + ANT_FAST_QUARQ_FREQUENCY, DEFAULT_NETWORK_NUMBER, "Fast Quarq", 'q', ":images/IconPower.png" }, { ANTChannel::CHANNEL_TYPE_FAST_QUARQ_NEW, ANT_FAST_QUARQ_PERIOD, ANT_FAST_QUARQ_TYPE_WAS, - ANT_FAST_QUARQ_FREQUENCY, DEFAULT_NETWORK_NUMBER, "Fast Quarq New", 'n' }, - { ANTChannel::CHANNEL_TYPE_GUARD, 0, 0, 0, 0, "", '\0' } + ANT_FAST_QUARQ_FREQUENCY, DEFAULT_NETWORK_NUMBER, "Fast Quarq New", 'n', ":images/IconPower.png" }, + { ANTChannel::CHANNEL_TYPE_GUARD, 0, 0, 0, 0, "", '\0', "" } }; // @@ -75,9 +80,10 @@ ANT::ANT(QObject *parent, DeviceConfiguration *devConf) : QThread(parent) { // device status and settings Status=0; - deviceFilename = devConf->portSpec; + deviceFilename = devConf ? devConf->portSpec : ""; baud=115200; powerchannels=0; + configuring = false; // state machine state = ST_WAIT_FOR_SYNC; @@ -85,7 +91,7 @@ ANT::ANT(QObject *parent, DeviceConfiguration *devConf) : QThread(parent) checksum = ANT_SYNC_BYTE; // ant ids - may not be configured of course - if (devConf->deviceProfile.length()) + if (devConf && devConf->deviceProfile.length()) antIDs = devConf->deviceProfile.split(","); else antIDs.clear(); @@ -98,17 +104,18 @@ ANT::ANT(QObject *parent, DeviceConfiguration *devConf) : QThread(parent) // connect up its signals connect(antChannel[i], SIGNAL(channelInfo(int,int,int)), this, SLOT(channelInfo(int,int,int))); - connect(antChannel[i], SIGNAL(dropInfo(int)), this, SLOT(dropInfo(int))); + connect(antChannel[i], SIGNAL(dropInfo(int,int,int)), this, SLOT(dropInfo(int,int,int))); connect(antChannel[i], SIGNAL(lostInfo(int)), this, SLOT(lostInfo(int))); connect(antChannel[i], SIGNAL(staleInfo(int)), this, SLOT(staleInfo(int))); connect(antChannel[i], SIGNAL(searchTimeout(int)), this, SLOT(slotSearchTimeout(int))); - connect(antChannel[i], SIGNAL(searchComplete(int)), this, SLOT(searchComplete(int))); + connect(antChannel[i], SIGNAL(searchComplete(int)), this, SLOT(slotSearchComplete(int))); } // on windows and linux we use libusb to read from USB2 // sticks, if it is not available we use stubs #if defined GC_HAVE_LIBUSB usbMode = USBNone; + channels = 0; usb2 = new LibUsb(TYPE_ANT); #endif } @@ -130,6 +137,14 @@ void ANT::setBaud(int x) baud = x; } +double ANT::channelValue2(int channel) +{ + return antChannel[channel]->channelValue2(); +} +double ANT::channelValue(int channel) +{ + return antChannel[channel]->channelValue(); +} /*====================================================================== * Main thread functions; start, stop etc @@ -138,13 +153,13 @@ void ANT::setBaud(int x) void ANT::run() { int status; // control commands from controller - bool isPortOpen = false; powerchannels = 0; Status = ANT_RUNNING; QString strBuf; #if defined GC_HAVE_LIBUSB usbMode = USBNone; + channels = 0; #endif for (int i=0; iinit(); @@ -158,7 +173,6 @@ void ANT::run() antlog.setFileName("antlog.bin"); antlog.open(QIODevice::WriteOnly | QIODevice::Truncate); - isPortOpen = true; sendMessage(ANTMessage::resetSystem()); sendMessage(ANTMessage::setNetworkKey(1, key)); @@ -178,11 +192,15 @@ void ANT::run() } else { - // not configured, just pair with whatever you can find - addDevice(0, ANTChannel::CHANNEL_TYPE_POWER, 0); - addDevice(0, ANTChannel::CHANNEL_TYPE_SandC, 1); - addDevice(0, ANTChannel::CHANNEL_TYPE_CADENCE, 2); - addDevice(0, ANTChannel::CHANNEL_TYPE_HR, 3); + if (!configuring) { + // not configured, just pair with whatever you can find + addDevice(0, ANTChannel::CHANNEL_TYPE_SPEED, 0); + addDevice(0, ANTChannel::CHANNEL_TYPE_POWER, 1); + addDevice(0, ANTChannel::CHANNEL_TYPE_CADENCE, 2); + addDevice(0, ANTChannel::CHANNEL_TYPE_HR, 3); + + if (channels > 4) addDevice(0, ANTChannel::CHANNEL_TYPE_SandC, 4); + } } } else { @@ -204,6 +222,13 @@ void ANT::run() status = this->Status; pvars.unlock(); + // do we have a channel to search / stop + if (!channelQueue.isEmpty()) { + setChannelAtom x = channelQueue.dequeue(); + if (x.device_number == -1) antChannel[x.channel]->close(); // unassign + else addDevice(x.device_number, x.channel_type, x.channel); // assign + } + /* time to shut up shop */ if (!(status&ANT_RUNNING)) { // time to stop! @@ -267,13 +292,6 @@ ANT::pause() int ANT::stop() { - int status; - - // get current status - pvars.lock(); - status = this->Status; - pvars.unlock(); - // what state are we in anyway? pvars.lock(); Status = 0; // Terminate it! @@ -316,7 +334,7 @@ ANT::addDevice(int device_number, int device_type, int channel_number) { // if we're given a channel number, then use that one if (channel_number>-1) { - antChannel[channel_number]->close(); + //antChannel[channel_number]->close(); antChannel[channel_number]->open(device_number, device_type); return 1; } @@ -327,7 +345,7 @@ ANT::addDevice(int device_number, int device_type, int channel_number) // on separate channels (e.g. 0p on channel 0 // and 0p on channel 1 if (device_number != 0) { - for (int i=0; ichannel_type & 0xf ) == device_type) && (antChannel[i]->device_number == device_number)) { // send the channel found... @@ -338,8 +356,10 @@ ANT::addDevice(int device_number, int device_type, int channel_number) } // look for an unused channel and use on that one - for (int i=0; ichannel_type == ANTChannel::CHANNEL_TYPE_UNUSED) { + + //antChannel[i]->close(); antChannel[i]->open(device_number, device_type); // this is an alternate channel for power @@ -366,7 +386,7 @@ ANT::removeDevice(int device_number, int channel_type) { int i; - for (i=0; ichannel_type == channel_type) && (ac->device_number == device_number)) { @@ -392,7 +412,7 @@ ANT::findDevice(int device_number, int channel_type) int i; - for (i=0; ichannel_type) == channel_type) && (antChannel[i]->device_number==device_number)) { return antChannel[i]; @@ -409,14 +429,14 @@ ANT::startWaitingSearch() int i; // are any fast searches in progress? if so, then bail - for (i=0; ichannel_type_flags & CHANNEL_TYPE_QUICK_SEARCH) { return 0; } } // start the first slow search - for (i=0; ichannel_type_flags & CHANNEL_TYPE_WAITING) { antChannel[i]->channel_type_flags &= ~CHANNEL_TYPE_WAITING; sendMessage(ANTMessage::unassignChannel(i)); @@ -430,7 +450,7 @@ ANT::startWaitingSearch() void ANT::report() { - for (int i=0; ichannelInfo(); ; } @@ -439,7 +459,7 @@ void ANT::associateControlChannels() { // first, unassociate all control channels - for (int i=0; icontrol_channel=NULL; + for (int i=0; icontrol_channel=NULL; // then, associate cinqos: // new cinqos get their own selves for control @@ -447,7 +467,7 @@ ANT::associateControlChannels() { // if found and open, associate // elif found and not open yet, nop // elif not found, open one - for (int i=0; ichannel_type) { @@ -489,11 +509,30 @@ ANT::associateControlChannels() { } // for-loop } -// XXX device discovery for pairing to do... need to -// think about a cool way to do this. +// For serial device discovery bool -ANT::discover(DeviceConfiguration *, QProgressDialog *) +ANT::discover(QString name) { +#ifdef Q_OS_LINUX + + // All we can do for USB1 sticks is see if the cp210x driver module + // is loaded for this device, and if it is, we will use the device + // XXX need a better way of probing this device, but USB1 sticks + // are getting rarer, so maybe we can just make do with this + // until we deprecate them altogether + struct stat s; + if (stat(name.toLatin1(), &s) == -1) return false; + int maj = major(s.st_rdev); + int min = minor(s.st_rdev); + QString sysFile = QString("/sys/dev/char/%1:%2/device/driver/module/drivers/usb:cp210x").arg(maj).arg(min); + if (QFileInfo(sysFile).exists()) return true; +#endif + +#ifdef Q_OS_MAC + // On MAC we only support the SILabs Virtual Com Port Drivers + // which will leave a device file /dev/cu.ANTUSBStick.slabvcp + if (name == "/dev/cu.ANTUSBStick.slabvcp") return true; +#endif return false; } @@ -506,41 +545,47 @@ ANT::channelInfo(int channel, int device_number, int device_id) } void -ANT::dropInfo(int /*number*/) // we dropped a message +ANT::dropInfo(int channel, int drops, int received) // we dropped a message { - return; // ignore for now, dropped messages are not so interesting + double reliability = 100.00f - (100.00f * double(drops) / double(received)); + //qDebug()<<"Channel"< 3) return; // ignore out of bound + if (number < 0 || number >= channels) return; // ignore out of bound emit lostDevice(number); - qDebug()<<"lost info for channel"< 3) return; // ignore out of bound + if (number < 0 || number >= channels) return; // ignore out of bound - qDebug()<<"stale info for channel"< 3) return; // ignore out of bound + if (number < 0 || number >= channels) return; // ignore out of bound emit searchTimeout(number); - qDebug()<<"search timeout on channel"<= channels) return; // ignore out of bound + + emit searchComplete(number); + //qDebug()<<"search completed on channel"<> send: "); -//for(int i=0; i= 0 && channel < 4) { + if(channel >= 0 && channel < channels) { // handle a channel event here! antChannel[channel]->receiveMessage(rxMessage); @@ -627,7 +673,7 @@ ANT::processMessage(void) { ANTMessage m(this, rxMessage); // for debug! //fprintf(stderr, "<< receive: "); -//for(int i=0; ifind() == true) return true; +#endif +#ifdef WIN32 + if (USBXpress::find() == true) return true; +#endif + return false; +} + int ANT::openPort() { #ifdef WIN32 @@ -710,12 +767,15 @@ int ANT::openPort() // on windows we try on USB2 then on USB1 then fail... if ((rc=usb2->open()) != -1) { usbMode = USB2; + channels = 8; return rc; } else if ((rc= USBXpress::open(&devicePort)) != -1) { usbMode = USB1; + channels = 4; return rc; } else { usbMode = USBNone; + channels = 0; return -1; } @@ -732,9 +792,11 @@ int ANT::openPort() int rc; if ((rc=usb2->open()) != -1) { usbMode = USB2; + channels = 8; return rc; } else { usbMode = USB1; + channels = 4; } #endif if ((devicePort=open(deviceFilename.toAscii(),O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) @@ -843,16 +905,15 @@ int ANT::rawRead(uint8_t bytes[], int size) return usb2->read((char *)bytes, size); } #endif - int timeout=0, i=0; + int i=0; uint8_t byte; // read one byte at a time sleeping when no data ready // until we timeout waiting then return error for (i=0; itype==type) return st->suffix; + } while (++st, st->type != ANTChannel::CHANNEL_TYPE_GUARD); + return '-'; +} + // convert ANT value to 'p' 'c' values char ANT::deviceTypeCode(int type) { diff --git a/src/ANT.h b/src/ANT.h index 51cff2c86..4b44786e7 100644 --- a/src/ANT.h +++ b/src/ANT.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,7 @@ typedef struct ant_sensor_type { int network; const char *descriptive_name; char suffix; + const char *iconname; } ant_sensor_type_t; @@ -101,6 +103,15 @@ static inline double get_timestamp( void ) { } +struct setChannelAtom { + setChannelAtom() : channel(0), device_number(0), channel_type(0) {} + setChannelAtom(int x, int y, int z) : channel(x), device_number(y), channel_type(z) {} + + int channel; + int device_number; + int channel_type; +}; + //====================================================================== // ANT Constants //====================================================================== @@ -164,7 +175,7 @@ static inline double get_timestamp( void ) { #define ANT_KEY_LENGTH 8 #define ANT_MAX_BURST_DATA 8 #define ANT_MAX_MESSAGE_SIZE 12 -#define ANT_MAX_CHANNELS 4 +#define ANT_MAX_CHANNELS 8 // Channel messages #define RESPONSE_NO_ERROR 0 @@ -241,6 +252,8 @@ signals: void foundDevice(int channel, int device_number, int device_id); // channelInfo void lostDevice(int channel); // dropInfo void searchTimeout(int channel); // searchTimeount + void searchComplete(int channel); // searchComplete + void signalStrength(int channel, double reliability); public slots: @@ -251,20 +264,33 @@ public slots: int stop(); // stops data collection thread int quit(int error); // called by thread before exiting - // channel management - bool discover(DeviceConfiguration *, QProgressDialog *); // confirm Server available at portSpec + // configuration and channel management + bool isConfiguring() { return configuring; } + void setConfigurationMode(bool x) { configuring = x; } + void setChannel(int channel, int device_number, int channel_type) { + channelQueue.enqueue(setChannelAtom(channel, device_number, channel_type)); + } + bool find(); // find usb device + bool discover(QString name); // confirm Server available at portSpec + + int channelCount() { return channels; } // how many channels we got available? void channelInfo(int number, int device_number, int device_id); // found a device - void dropInfo(int number); // we dropped a connection + void dropInfo(int number, int drops, int received); // we dropped a connection void lostInfo(int number); // we lost informa void staleInfo(int number); // info is now stale void slotSearchTimeout(int number); // search timed out - void searchComplete(int number); // search completed successfully + void slotSearchComplete(int number); // search completed successfully // get telemetry void getRealtimeData(RealtimeData &); // return current realtime data public: + static int interpretSuffix(char c); // utility to convert e.g. 'c' to CHANNEL_TYPE_CADENCE + static const char *deviceTypeDescription(int type); // utility to convert CHANNEL_TYPE_XXX to human string + static char deviceTypeCode(int type); // utility to convert CHANNEL_TYPE_XXX to 'c', 'p' et al + static char deviceIdCode(int type); // utility to convert CHANNEL_TYPE_XXX to 'c', 'p' et al + // debug enums enum { DEBUG_LEVEL_ERRORS=1, DEBUG_LEVEL_ANT_CONNECTION=2, @@ -300,6 +326,8 @@ public: int rawWrite(uint8_t *bytes, int size); // channels update our telemetry + double channelValue(int channel); + double channelValue2(int channel); void setBPM(float x) { telemetry.setHr(x); } @@ -320,13 +348,12 @@ public: private: void run(); - static int interpretSuffix(char c); // utility to convert e.g. 'c' to CHANNEL_TYPE_CADENCE - static const char *deviceTypeDescription(int type); // utility to convert CHANNEL_TYPE_XXX to human string - static char deviceTypeCode(int type); // utility to convert CHANNEL_TYPE_XXX to 'c', 'p' et al RealtimeData telemetry; QMutex pvars; // lock/unlock access to telemetry data between thread and controller int Status; // what status is the client in? + bool configuring; // set to true if we're in configuration mode. + int channels; // how many 4 or 8 ? depends upon the USB stick... // access to device file QString deviceFilename; @@ -361,6 +388,8 @@ private: int checksum; int powerchannels; // how many power channels do we have? + QQueue channelQueue; // messages for configuring channels from controller + // antlog.bin ant message stream QFile antlog; }; diff --git a/src/ANTChannel.cpp b/src/ANTChannel.cpp index 978959dad..948c1a650 100644 --- a/src/ANTChannel.cpp +++ b/src/ANTChannel.cpp @@ -43,6 +43,7 @@ ANTChannel::init() product_id=0; product_version=0; device_number=0; + device_id=0; channel_assigned=0; state=ANT_UNASSIGN_CHANNEL; blanked=1; @@ -51,6 +52,7 @@ ANTChannel::init() setId(); srm_offset=400; // default relatively arbitrary, but matches common 'indoors' values burstInit(); + value2=value=0; } // @@ -94,11 +96,13 @@ void ANTChannel::open(int device, int chan_type) setId(); +#if 0 if (channel_assigned) { parent->sendMessage(ANTMessage::unassignChannel(number)); } else { +#endif attemptTransition(ANT_UNASSIGN_CHANNEL); - } + //} } // close an ant channel assignment @@ -107,6 +111,7 @@ void ANTChannel::close() emit lostInfo(number); lastMessage = ANTMessage(); parent->sendMessage(ANTMessage::close(number)); + init(); } // @@ -140,6 +145,7 @@ void ANTChannel::receiveMessage(unsigned char *ant_message) if (get_timestamp() > blanking_timestamp + timeout_blanking) { if (!blanked) { blanked=1; + value2=value=0; emit staleInfo(number); } } else blanked=0; @@ -153,6 +159,8 @@ void ANTChannel::channelEvent(unsigned char *ant_message) { unsigned char *message=ant_message+2; +//qDebug()<<"channel event:"<< ANTMessage::channelEventMessage(*(message+1)); + if (MESSAGE_IS_RESPONSE_NO_ERROR(message)) { attemptTransition(RESPONSE_NO_ERROR_MESSAGE_ID(message)); @@ -178,6 +186,7 @@ void ANTChannel::channelEvent(unsigned char *ant_message) { channel_type=CHANNEL_TYPE_UNUSED; channel_type_flags=0; device_number=0; + value2=value=0; setId(); parent->sendMessage(ANTMessage::unassignChannel(number)); @@ -191,7 +200,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) emit dropInfo(number); + if (channel_type != CHANNEL_TYPE_UNUSED) emit dropInfo(number, messages_dropped, messages_received); // this is a hacky way to prevent the drop message from sending multiple times last_message_timestamp+=2*timeout_drop; } @@ -244,7 +253,6 @@ void ANTChannel::sendCinqoSuccess() {} // void ANTChannel::broadcastEvent(unsigned char *ant_message) { - ANTMessage antMessage(parent, ant_message); bool savemessage = true; // flag to stop lastmessage being // overwritten for standard power @@ -256,15 +264,21 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) messages_received++; last_message_timestamp=timestamp; - if (state != MESSAGE_RECEIVED) { - //qDebug()<<"state not equal to message received"; - // first message! who are we talking to? + if (messages_received <= 1) { + + // this is mega important! -- when we get broadcast data from a device + // we ask it to identify itself, then when the channel id message is + // received we set our channel id to that received. So, if the message + // below is not sent, we will never set channel properly. + + // The recent bug with not being able to "pair" intermittently, was caused + // by the write below failing (and any write really, but the one below being + // pretty critical) -- because the USB stick needed a USB reset which we know + // do every time we open the USB device parent->sendMessage(ANTMessage::requestMessage(number, ANT_CHANNEL_ID)); blanking_timestamp=get_timestamp(); blanked=0; return; // because we can't associate a channel id with the message yet - } else { - //qDebug()<<"state IS equal to message received"; } // for automatically opening quarq channel on early cinqo @@ -324,6 +338,7 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) srm_offset = antMessage.srmOffset; is_alt ? parent->setAltWatts(0) : parent->setWatts(0); parent->setCadence(0); + value2=value=0; break; case 0x02: // slope @@ -365,6 +380,7 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) // ignore the occassional spikes XXX is this a boundary error on event count ? if (power >= 0 && power < 2501 && cadence >=0 && cadence < 256) { + value2 = value = power; is_alt ? parent->setAltWatts(power) : parent->setWatts(power); parent->setCadence(cadence); } @@ -375,6 +391,7 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) //antMessage.type = 0; // we need a new data pair XXX bad!!! if (nullCount >= 4) { // 4 messages on an SRM + value2 = value = 0; is_alt ? parent->setAltWatts(0) : parent->setWatts(0); parent->setCadence(0); } @@ -399,6 +416,7 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) float wheelRPM = 2048.0 * 60.0 * events / period; float power = 3.14159 * nm_torque * wheelRPM / 30; + value2 = value = power; parent->setWheelRpm(wheelRPM); is_alt ? parent->setAltWatts(power) : parent->setWatts(power); @@ -407,6 +425,7 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) if (nullCount >= 4) { // 4 messages on Powertap ? XXX validate this parent->setWheelRpm(0); + value2 = value = 0; is_alt ? parent->setAltWatts(0) : parent->setWatts(0); } } @@ -425,6 +444,7 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) { if (lastStdPwrMessage.type != 0) { is_alt ? parent->setAltWatts(antMessage.instantPower) : parent->setWatts(antMessage.instantPower); + value2 = value = antMessage.instantPower; parent->setCadence(antMessage.instantCadence); // cadence } lastStdPwrMessage = antMessage; @@ -451,12 +471,14 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) parent->setCadence(cadence); is_alt ? parent->setAltWatts(power) : parent->setWatts(power); + value2 = value = power; } else { nullCount++; if (nullCount >= 4) { //XXX 4 on a quarq??? validate this parent->setCadence(0); is_alt ? parent->setAltWatts(0) : parent->setWatts(0); + value2 = value = 0; } } } @@ -475,9 +497,13 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) if (time) { nullCount = 0; parent->setBPM(antMessage.instantHeartrate); + value2 = value = antMessage.instantHeartrate; } else { nullCount++; - if (nullCount >= 12) parent->setBPM(0); // 12 according to the docs + if (nullCount >= 12) { + parent->setBPM(0); // 12 according to the docs + value2 = value = 0; + } } } break; @@ -490,6 +516,7 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) if (time) { float cadence = 1024*60*revs / time; parent->setCadence(cadence); + value2 = value = cadence; } } break; @@ -504,9 +531,12 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) nullCount = 0; float cadence = 1024*60*revs / time; parent->setCadence(cadence); + value = cadence; } else { nullCount++; - if (nullCount >= 12) parent->setCadence(0); + if (nullCount >= 12) { parent->setCadence(0); + value = 0; + } } // now speed ... @@ -517,10 +547,14 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) float rpm = 1024*60*revs / time; parent->setWheelRpm(rpm); + value2 = rpm; } else { dualNullCount++; - if (dualNullCount >= 12) parent->setWheelRpm(0); + if (dualNullCount >= 12) { + parent->setWheelRpm(0); + value2 = 0; + } } } break; @@ -534,9 +568,11 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) nullCount=0; float rpm = 1024*60*revs / time; parent->setWheelRpm(rpm); + value2=value=rpm; } else { nullCount++; if (nullCount >= 12) parent->setWheelRpm(0); + value2=value=0; } } break; @@ -549,6 +585,7 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) // reset nullCount if receiving first telemetry update dualNullCount = nullCount = 0; + value2 = value = 0; } // we don't overwrite for Standard Power messages @@ -570,9 +607,8 @@ void ANTChannel::channelId(unsigned char *ant_message) { device_number=CHANNEL_ID_DEVICE_NUMBER(message); device_id=CHANNEL_ID_DEVICE_TYPE_ID(message); state=MESSAGE_RECEIVED; - - setId(); emit channelInfo(number, device_number, device_id); + setId(); // if we were searching, if (channel_type_flags & CHANNEL_TYPE_QUICK_SEARCH) { @@ -629,6 +665,13 @@ void ANTChannel::burstData(unsigned char *ant_message) { } } +// choose this one.. +void ANTChannel::setChannelID(int device_number, int device_id, int txtype) +{ + parent->sendMessage(ANTMessage::setChannelID(number, device_number, device_id, 0)); // lets go back to allowing anything + parent->sendMessage(ANTMessage::open(number)); // lets go back to allowing anything +} + void ANTChannel::attemptTransition(int message_id) { @@ -651,21 +694,16 @@ void ANTChannel::attemptTransition(int message_id) case ANT_UNASSIGN_CHANNEL: channel_assigned=0; - if (st->type==CHANNEL_TYPE_UNUSED) { - // we're shutting the channel down - } else { - device_id=st->device_id; - if (channel_type & CHANNEL_TYPE_PAIR) { - device_id |= 0x80; - } - setId(); - parent->sendMessage(ANTMessage::assignChannel(number, 0, st->network)); // recieve channel on network 1 - // 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 - } + // lets make sure this channel is assigned to our network + // regardless of its current state. + parent->sendMessage(ANTMessage::unassignChannel(number)); // unassign whatever we had before + + // reassign to whatever we need! + parent->sendMessage(ANTMessage::assignChannel(number, 0, st->network)); // recieve channel on network 1 + device_id=st->device_id; + parent->sendMessage(ANTMessage::setChannelID(number, 0, device_id, 0)); // lets go back to allowing anything + setId(); break; case ANT_ASSIGN_CHANNEL: @@ -702,6 +740,7 @@ void ANTChannel::attemptTransition(int message_id) break; case ANT_OPEN_CHANNEL: + parent->sendMessage(ANTMessage::open(number)); break; default: @@ -709,8 +748,11 @@ void ANTChannel::attemptTransition(int message_id) } } -// refactored out XXX fix this -int ANTChannel::setTimeout(char * /*type*/, float /*value*/, int /*connection*/) { return 0; } +// set channel timeout +int ANTChannel::setTimeout(int seconds) +{ + parent->sendMessage(ANTMessage::setSearchTimeout(number, seconds/2.5)); +} #if 0 // ARE NOW SIGNALS // These should emit signals to notify the channel manager diff --git a/src/ANTChannel.h b/src/ANTChannel.h index c1d3f9915..6e8874ef1 100644 --- a/src/ANTChannel.h +++ b/src/ANTChannel.h @@ -152,10 +152,16 @@ class ANTChannel : public QObject { void broadcastEvent(unsigned char *message); void ackEvent(unsigned char *message); void channelId(unsigned char *message); + void setChannelID(int device, int id, int txtype); void setId(); void requestCalibrate(); void attemptTransition(int message_code); - int setTimeout(char *type, float value, int connection); + int setTimeout(int seconds); + + // telemetry for this channel + double channelValue() { return value; } + double channelValue2() { return value2; } + double value,value2; // used during config, rather than rtData // search int isSearching(); @@ -170,7 +176,7 @@ class ANTChannel : public QObject { signals: void channelInfo(int number, int device_number, int device_id); // we got a channel info message - void dropInfo(int number); // we dropped a packet + void dropInfo(int number, int dropped, int received); // we dropped a packet void lostInfo(int number); // we lost a connection void staleInfo(int number); // the connection is stale void searchTimeout(int number); // search timed out diff --git a/src/ANTMessage.h b/src/ANTMessage.h index 3e5fed386..a3659c2ae 100644 --- a/src/ANTMessage.h +++ b/src/ANTMessage.h @@ -69,7 +69,7 @@ class ANTMessage { static ANTMessage close(const unsigned char channel); // convert a channel event message id to human readable string - const char * channelEventMessage(unsigned char c); + static 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 diff --git a/src/ANTlocalController.cpp b/src/ANTlocalController.cpp index 201e1712b..4980a532f 100644 --- a/src/ANTlocalController.cpp +++ b/src/ANTlocalController.cpp @@ -25,8 +25,16 @@ ANTlocalController::ANTlocalController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) { myANTlocal = new ANT (parent, dc); + connect(myANTlocal, SIGNAL(foundDevice(int,int,int)), this, SIGNAL(foundDevice(int,int,int))); + connect(myANTlocal, SIGNAL(lostDevice(int)), this, SIGNAL(lostDevice(int))); + connect(myANTlocal, SIGNAL(searchTimeout(int)), this, SIGNAL(searchTimeout(int))); } +void +ANTlocalController::setDevice(QString device) +{ + myANTlocal->setDevice(device); +} int ANTlocalController::start() @@ -56,11 +64,16 @@ ANTlocalController::stop() return myANTlocal->stop(); } +bool +ANTlocalController::find() +{ + return myANTlocal->find(); +} bool -ANTlocalController::discover(DeviceConfiguration *dc, QProgressDialog *progress) +ANTlocalController::discover(QString name) { - return myANTlocal->discover(dc, progress); + return myANTlocal->discover(name); } diff --git a/src/ANTlocalController.h b/src/ANTlocalController.h index 5d3d1c097..631348e57 100644 --- a/src/ANTlocalController.h +++ b/src/ANTlocalController.h @@ -24,12 +24,12 @@ // Abstract base class for Realtime device controllers - #ifndef _GC_ANTlocalController_h #define _GC_ANTlocalController_h 1 class ANTlocalController : public RealtimeController { + Q_OBJECT public: ANTlocalController (TrainTool *parent =0, DeviceConfiguration *dc =0); @@ -40,14 +40,32 @@ public: int restart(); // restart after paused int pause(); // pauses data collection, inbound telemetry is discarded int stop(); // stops data collection thread - bool discover(DeviceConfiguration *dc =0, QProgressDialog *progress = 0); + + int channels() { return myANTlocal->channelCount(); } + void setChannel(int channel, int device_number, int device_type) { + myANTlocal->setChannel(channel,device_number,device_type); // using QQueue + } + double channelValue(int channel) { return myANTlocal->channelValue(channel); } + double channelValue2(int channel) { return myANTlocal->channelValue2(channel); } + + bool find(); + bool discover(QString name); + void setDevice(QString); // telemetry push pull bool doesPush(), doesPull(), doesLoad(); void getRealtimeData(RealtimeData &rtData); void pushRealtimeData(RealtimeData &rtData); void setLoad(double) { return; } + +signals: + void foundDevice(int channel, int device_number, int device_id); // channelInfo + void lostDevice(int channel); // dropInfo + void searchTimeout(int channel); // searchTimeount + +private: + QQueue channelQueue; + }; #endif // _GC_ANTlocalController_h - diff --git a/src/AddDeviceWizard.cpp b/src/AddDeviceWizard.cpp new file mode 100644 index 000000000..b93df47e5 --- /dev/null +++ b/src/AddDeviceWizard.cpp @@ -0,0 +1,874 @@ +/* + * Copyright (c) 2012 Mark Liversedge (liversedge@gmail.com) + * + * 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 "AddDeviceWizard.h" + +// WIZARD FLOW +// +// 10. Select Device Type +// 20. Scan for Device / select Serial +// 30. Firmware for Fortius +// 50. Pair for ANT +// 60. Finalise +// + +// Main wizard +AddDeviceWizard::AddDeviceWizard(MainWindow *main, DeviceConfiguration &here) : QWizard(main), main(main), here(here) +{ +#ifdef Q_OS_MAC + setWizardStyle(QWizard::ModernStyle); +#endif + + // delete when done + setAttribute(Qt::WA_DeleteOnClose); + + setFixedHeight(500); + setFixedWidth(550); + + // title + setWindowTitle(tr("Add Device Wizard")); + scanner = new DeviceScanner(this); + + setPage(10, new AddType(this)); // done + setPage(20, new AddSearch(this)); // done + setPage(30, new AddFirmware(this)); // done + setPage(50, new AddPair(this)); // todo + setPage(60, new AddFinal(this)); // todo -- including virtual power + + done = false; + + type = -1; + current = 0; + controller = NULL; + +} + +/*---------------------------------------------------------------------- + * Wizard Pages + *--------------------------------------------------------------------*/ + +//Select device type +AddType::AddType(AddDeviceWizard *parent) : QWizardPage(parent), wizard(parent) +{ + setTitle(tr("Select Device")); + setSubTitle(tr("What kind of device to add")); + + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + + mapper = new QSignalMapper(this); + connect(mapper, SIGNAL(mapped(QString)), this, SLOT(clicked(QString))); + + foreach(DeviceType t, wizard->deviceTypes.Supported) { + if (t.type) { + QCommandLinkButton *p = new QCommandLinkButton(t.name, t.description, this); + connect(p, SIGNAL(clicked()), mapper, SLOT(map())); + mapper->setMapping(p, QString("%1").arg(t.type)); + layout->addWidget(p); + } + } + label = new QLabel("", this); + layout->addWidget(label); + + next = 20; + setFinalPage(false); +} + +void +AddType::initializePage() +{ + // reset any device search info + wizard->portSpec = ""; + wizard->found = false; + wizard->current = 0; + if (wizard->controller) { + delete wizard->controller; + wizard->controller = NULL; + } +} + + +void +AddType::clicked(QString p) +{ + // reset -- particularly since we might get here from + // other pages hitting 'Back' + initializePage(); + wizard->type = p.toInt(); + + // what are we scanning for? + int i=0; + foreach(DeviceType t, wizard->deviceTypes.Supported) { + if (t.type == wizard->type) wizard->current = i; + i++; + } + + wizard->found = wizard->scanner->quickScan(false); // do a quick scan + + // Still no dice. Go to the not found dialog + if (wizard->found == false) next =20; + else { + switch(wizard->deviceTypes.Supported[wizard->current].type) { + case DEV_ANTLOCAL : next = 50; break; // pair + default: + case DEV_CT : next = 60; break; // confirm and add + case DEV_FORTIUS : next = 30; break; // confirm and add + } + } + wizard->next(); +} + +DeviceScanner::DeviceScanner(AddDeviceWizard *wizard) : wizard(wizard) {} + +void +DeviceScanner::run() +{ + active = true; + bool result = false; + for (int i=0; active && !result && i<10; i++) { + + sleep(1); // better to wait a while, esp. if its just a USB device + result = quickScan(false); + } + if (active) emit finished(result); // only signal if we weren't aborted! +} + +void +DeviceScanner::stop() +{ + active = false; +} + + +bool +DeviceScanner::quickScan(bool deep) // scan quickly or if true scan forever, as deep as possible + // for now deep just means try 3 time before giving up, but we + // may want to change that to include scanning more devices? +{ + // get controller + if (wizard->controller) { + delete wizard->controller; + wizard->controller=NULL; + } + + switch (wizard->deviceTypes.Supported[wizard->current].type) { + + // we will need a factory for this soon.. + case DEV_ANTPLUS : wizard->controller = new ANTplusController(NULL, NULL); break; + case DEV_CT : wizard->controller = new ComputrainerController(NULL, NULL); break; +#ifdef GC_HAVE_LIBUSB + case DEV_FORTIUS : wizard->controller = new FortiusController(NULL, NULL); break; +#endif + case DEV_NULL : wizard->controller = new NullController(NULL, NULL); break; + case DEV_ANTLOCAL : wizard->controller = new ANTlocalController(NULL, NULL); break; + + default: wizard->controller = NULL; break; + + } + + + //---------------------------------------------------------------------- + // Search for USB devices + //---------------------------------------------------------------------- + + bool isfound = false; + int count=0; + do { + + // can we find it automatically? + isfound = wizard->controller->find(); + + if (isfound == false && (wizard->deviceTypes.Supported[wizard->current].connector == DEV_LIBUSB || + wizard->deviceTypes.Supported[wizard->current].connector == DEV_USB)) { + + // Go to next page where we do not found, rescan and manual override + if (!deep) return false; + } + + + //---------------------------------------------------------------------- + // Search serial ports + //---------------------------------------------------------------------- + + if (isfound == false && wizard->deviceTypes.Supported[wizard->current].connector == DEV_SERIAL) { + + // automatically discover a serial port ... + QString error; + foreach (CommPortPtr port, Serial::myListCommPorts(error)) { + + if (wizard->controller->discover(port->name()) == true) { + isfound = true; + wizard->portSpec = port->name(); + break; + } + } + + // if we still didn't find it then we need to fall back to the user + // specifying the device on the next page + } + + } while (!isfound && deep && count++ < 2); + + return isfound; + +} + +// Scan for device port / usb etc +AddSearch::AddSearch(AddDeviceWizard *parent) : QWizardPage(parent), wizard(parent) +{ + setSubTitle(tr("Scan for connected devices")); + + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + + isSearching = false; + active = false; + + + label = new QLabel("Please make sure your device is connected, switched on and working. " + "We will scan for the device type you have selected at known ports.\n\n"); + label->setWordWrap(true); + layout->addWidget(label); + + bar = new QProgressBar(this); + bar->setMaximum(100); + bar->setMinimum(0); + bar->setValue(0); + //bar->setText("Searching..."); + layout->addWidget(bar); + + QHBoxLayout *hlayout2 = new QHBoxLayout; + stop = new QPushButton("Search", this); + hlayout2->addStretch(); + hlayout2->addWidget(stop); + layout->addLayout(hlayout2); + + label1 = new QLabel("If your device is not found you can select the device port " + "manually by using the selection box below."); + label1->setWordWrap(true); + layout->addWidget(label1); + + label2 = new QLabel("\nDevice found.\nClick Next to Continue\n"); + label2->hide(); + label2->setWordWrap(true); + layout->addWidget(label2); + + QHBoxLayout *hlayout = new QHBoxLayout; + manual = new QComboBox(this); + hlayout->addStretch(); + hlayout->addWidget(manual); + layout->addLayout(hlayout); + + layout->addStretch(); + + connect(stop, SIGNAL(clicked()), this, SLOT(doScan())); + connect(manual, SIGNAL(currentIndexChanged(int)), this, SLOT(chooseCOMPort())); + connect(wizard->scanner, SIGNAL(finished(bool)), this, SLOT(scanFinished(bool))); + +} + +void +AddSearch::chooseCOMPort() +{ + if (active) return; + + if (manual->currentIndex() <= 0) { // we unselected or something. + wizard->found = false; + wizard->portSpec = ""; + emit completeChanged(); + return; + } + + // stop any scan that may be in process? + if (isSearching == true) { + doScan(); // remember doScan toggles with the stop/search again button + } + + // let the user select the port + wizard->portSpec = manual->itemText(manual->currentIndex()); + + // carry on then + wizard->found = true; // ugh + + emit completeChanged(); +} + +void +AddSearch::initializePage() +{ + active = true; + setTitle(QString(tr("%1 Search")).arg(wizard->deviceTypes.Supported[wizard->current].name)); + + // we only ask for the device file if it is a serial device + if (wizard->deviceTypes.Supported[wizard->current].connector == DEV_SERIAL) { + + // wipe away whatever items it has now + for (int i=manual->count(); i > 0; i--) manual->removeItem(0); + + // add in the items we have.. + manual->addItem("Select COM port"); + QString error; + foreach (CommPortPtr port, Serial::myListCommPorts(error)) manual->addItem(port->name()); + manual->show(); + label1->show(); + + + } else { + label1->hide(); + manual->hide(); + } + + bar->show(); + stop->show(); + label->show(); + label2->hide(); + active = false; + doScan(); +} + +void +AddSearch::scanFinished(bool result) +{ + isSearching = false; + wizard->found = result; + bar->setMaximum(100); + bar->setMinimum(0); + bar->setValue(0); + stop->setText("Search again"); + + if (result == true) { // woohoo we found one + bar->hide(); + stop->hide(); + manual->hide(); + label->hide(); + label1->hide(); + if (wizard->portSpec != "") + label2->setText(QString("\nDevice found on %1.\nPress Next to Continue\n").arg(wizard->portSpec)); + else + label2->setText("\nDevice found.\nPress Next to Continue\n"); + label2->show(); + } + QApplication::processEvents(); + emit completeChanged(); +} + +void +AddSearch::doScan() +{ + if (isSearching == false) { // start a scan + + // make bar bouncy... + bar->setMaximum(0); + bar->setMinimum(0); + bar->setValue(0); + stop->setText("Stop Searching"); + isSearching = true; + manual->setCurrentIndex(0); //deselect any chosen port + wizard->found = false; + wizard->portSpec = ""; + + wizard->scanner->start(); + + } else { // stop a scan + + isSearching = false; + // make bar stationary... + bar->setMaximum(100); + bar->setMinimum(0); + bar->setValue(0); + stop->setText("Search again"); + + wizard->scanner->stop(); + } +} + +int +AddSearch::nextId() const +{ + // Still no dice. Go to the not found dialog + if (wizard->found == false) return -1; + else { + switch(wizard->deviceTypes.Supported[wizard->current].type) { + case DEV_ANTLOCAL : return 50; break; // pair + default: + case DEV_CT : return 60; break; // confirm and add + case DEV_FORTIUS : return 30; break; // confirm and add + } + } +} + +bool +AddSearch::validatePage() +{ + return wizard->found; +} + +void +AddSearch::cleanupPage() +{ + wizard->scanner->stop(); + if (isSearching) sleep(2); // give it time to stop... + isSearching=false; + if (wizard->controller) { + delete wizard->controller; + wizard->controller = NULL; + } +} + +// Fortius Firmware +AddFirmware::AddFirmware(AddDeviceWizard *parent) : QWizardPage(parent), wizard(parent) +{ + setTitle(tr("Select Firmware")); + setSubTitle(tr("Select Firmware for Tacx Fortius")); + + // create widgets + browse = new QPushButton("Browse", this); + copy = new QCheckBox("Copy to Library"); + copy->setChecked(true); + + help = new QLabel(this); + help->setWordWrap(true); + help->setText("Tacx Fortius trainers require a firmware file " + "which is provided by Tacx BV. This file is a " + "copyrighted file and cannot be distributed with " + "GoldenCheetah.\n\n" + "On windows it is typically installed in C:\\Windows\\system32 " + "and is called 'FortiusSWPID1942Renum.hex'.\n\n" +#if defined Q_OS_LINUX || defined Q_OS_MAC + "On Linux and Apple computers you will need to " + "extract it from the VR Software CD." + "The file we need is within the 'data2.cab' file, " + "which is an InstallShield file that can be read " + "with the 'unshield' tool\n\n" +#endif + "Please take care to ensure that the file is the latest version " + "of the Firmware file.\n\n" + "If you choose to copy to library the file will be copied into the " + "GoldenCheetah library, otherwise we will reference it. "); + + file = new QLabel("File:", this); + + name= new QLineEdit(this); + name->setEnabled(false); + + QString fortiusFirmware = appsettings->value(this, FORTIUS_FIRMWARE, "").toString(); + name->setText(fortiusFirmware); + + // Layout widgets + QHBoxLayout *buttons = new QHBoxLayout; + QHBoxLayout *filedetails = new QHBoxLayout; + filedetails->addWidget(file); + filedetails->addWidget(name); + filedetails->addWidget(browse); + filedetails->addStretch(); + + buttons->addWidget(copy); + buttons->addStretch(); + + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->addLayout(filedetails); + mainLayout->addWidget(help); + mainLayout->addStretch(); + mainLayout->addLayout(buttons); + + // connect widgets + connect(browse, SIGNAL(clicked()), this, SLOT(browseClicked())); +} + +bool +AddFirmware::validatePage() +{ + QString filePath = name->text(); + if (filePath == "" || !QFile(filePath).exists()) return false; + + // either copy it, or reference it! + if (copy->isChecked()) { + + QString fileName = QFileInfo(filePath).fileName(); + QString targetFileName = QFileInfo(mainWindow->home.absolutePath() + "/../").absolutePath() + "/" + fileName; + + // check not the same thing! + if(QFileInfo(fileName).absolutePath() != QFileInfo(targetFileName).absolutePath()) { + // if the current file exists, wipe it + if (QFile(targetFileName).exists()) QFile(targetFileName).remove(); + QFile(filePath).copy(targetFileName); + } + name->setText(targetFileName); + } + appsettings->setValue(FORTIUS_FIRMWARE, name->text()); + return true; +} + +void +AddFirmware::browseClicked() +{ + QString file = QFileDialog::getOpenFileName(this, tr("Open File"), "", tr("Intel Firmware File (*.hex)")); + if (file != "") name->setText(file); +} + +// Pair devices +AddPair::AddPair(AddDeviceWizard *parent) : QWizardPage(parent), wizard(parent) +{ + setTitle(tr("Pair Devices")); + setSubTitle(tr("Search for and pair ANT+ devices")); + + signalMapper = NULL; + + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + + channelWidget = new QTreeWidget(this); + layout->addWidget(channelWidget); +} + +static void +addSensorTypes(ANT *ant, QComboBox *p) +{ + for (int i=0; ant->ant_sensor_types[i].suffix != '\0'; i++) { + if (*ant->ant_sensor_types[i].iconname != '\0') { + p->addItem(QIcon(ant->ant_sensor_types[i].iconname), ant->ant_sensor_types[i].descriptive_name, ant->ant_sensor_types[i].type); + } else { + p->addItem(ant->ant_sensor_types[i].descriptive_name, ant->ant_sensor_types[i].type); + } + } +} + +void +AddPair::cleanupPage() +{ + updateValues.stop(); + if (wizard->controller) { + wizard->controller->stop(); + sleep(1); + delete wizard->controller; + wizard->controller = NULL; + } +} + +static void enableDisable(QTreeWidget *tree) +{ + // enable disable widgets based upon sensor selection + for (int i=0; i< tree->invisibleRootItem()->childCount(); i++) { + QTreeWidgetItem *item = tree->invisibleRootItem()->child(i); + + // is it selected or not? + bool enable = (dynamic_cast(tree->itemWidget(item,0))->currentIndex() != 0); + + // enable all thos widgetry + tree->itemWidget(item,2)->setEnabled(enable); // value + tree->itemWidget(item,3)->setEnabled(enable); // status + } +} + +void +AddPair::initializePage() +{ + // setup the controller and start it off so we can + // manipulate it + if (wizard->controller) delete wizard->controller; + if (signalMapper) delete signalMapper; + wizard->controller = new ANTlocalController(NULL,NULL); + dynamic_cast(wizard->controller)->setDevice(wizard->portSpec); + dynamic_cast(wizard->controller)->myANTlocal->setConfigurationMode(true); //XXX + wizard->controller->start(); + wizard->profile=""; // clear any thing thats there now + signalMapper = new QSignalMapper(this); + + // Channel 0, look for any (0 devicenumber) speed and distance device + + sleep(1); // wait for it to start... + int channels = dynamic_cast(wizard->controller)->channels(); + + // Tree Widget of the channel controls + channelWidget->clear(); + channelWidget->headerItem()->setText(0, tr("Sensor")); + channelWidget->headerItem()->setText(1, tr("ANT+ Id")); + channelWidget->headerItem()->setText(2, tr("Value")); + channelWidget->headerItem()->setText(3, tr("Status")); + channelWidget->setColumnCount(4); + channelWidget->setSelectionMode(QAbstractItemView::NoSelection); + //channelWidget->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit + channelWidget->setUniformRowHeights(true); + channelWidget->setIndentation(0); + + channelWidget->header()->resizeSection(0,175); // type + channelWidget->header()->resizeSection(1,75); // id + channelWidget->header()->resizeSection(2,120); // value + channelWidget->header()->resizeSection(3,110); // status + + // defaults + static const int index4[4] = { 1,2,3,5 }; + static const int index8[8] = { 1,2,3,4,5,0,0,0 }; + const int *index = channels == 4 ? index4 : index8; + + // how many devices we got then? + for (int i=0; i< channels; i++) { + + QTreeWidgetItem *add = new QTreeWidgetItem(channelWidget->invisibleRootItem()); + add->setFlags(add->flags() | Qt::ItemIsEditable); + + // sensor type + QComboBox *sensorSelector = new QComboBox(this); + addSensorTypes(dynamic_cast(wizard->controller)->myANTlocal, sensorSelector); + sensorSelector->setCurrentIndex(index[i]); + channelWidget->setItemWidget(add, 0, sensorSelector); + + // sensor id + QLineEdit *sensorId = new QLineEdit(this); + sensorId->setEnabled(false); + sensorId->setText("none"); + channelWidget->setItemWidget(add, 1, sensorId); + + // value + QLabel *value = new QLabel(this); + QFont bigger; + bigger.setPointSize(25); + value->setFont(bigger); + value->setAlignment(Qt::AlignCenter | Qt::AlignVCenter); + value->setText("0"); + channelWidget->setItemWidget(add, 2, value); + + // status + QLabel *status = new QLabel(this); + status->setText("Un-Paired"); + channelWidget->setItemWidget(add, 3, status); + + //channelWidget->verticalHeader()->resizeSection(i,40) + connect(sensorSelector, SIGNAL(currentIndexChanged(int)), signalMapper, SLOT(map())); + signalMapper->setMapping(sensorSelector, i); + } + channelWidget->setCurrentItem(channelWidget->invisibleRootItem()->child(0)); + enableDisable(channelWidget); + + updateValues.start(200); // 5hz + connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(sensorChanged(int))); + connect(&updateValues, SIGNAL(timeout()), this, SLOT(getChannelValues())); + connect(wizard->controller, SIGNAL(foundDevice(int,int,int)), this, SLOT(channelInfo(int,int,int))); + connect(wizard->controller, SIGNAL(searchTimeout(int)), this, SLOT(searchTimeout(int))); + //connect(wizard->controller, SIGNAL(lostDevice(int)), this, SLOT(searchTimeout(int))); + + // now we're ready to get notifications - set channels + for (int i=0; iinvisibleRootItem()->child(channel); + enableDisable(channelWidget); + + // first off lets unassign this channel + dynamic_cast(wizard->controller)->myANTlocal->setChannel(channel, -1, 0); + dynamic_cast(channelWidget->itemWidget(item,1))->setText("none"); + dynamic_cast(channelWidget->itemWidget(item,2))->setText(0); + + // what is it then? unused or restart scan? + QComboBox *p = dynamic_cast(channelWidget->itemWidget(item,0)); + int channel_type = p->itemData(p->currentIndex()).toInt(); + if (channel_type == ANTChannel::CHANNEL_TYPE_UNUSED) { + dynamic_cast(channelWidget->itemWidget(item,3))->setText("Unused"); + } else { + dynamic_cast(channelWidget->itemWidget(item,3))->setText("Searching..."); + dynamic_cast(wizard->controller)->myANTlocal->setChannel(channel, 0, channel_type); + } +} + +void +AddPair::channelInfo(int channel, int device_number, int device_id) +{ + Q_UNUSED(device_id); + QTreeWidgetItem *item = channelWidget->invisibleRootItem()->child(channel); + dynamic_cast(channelWidget->itemWidget(item,1))->setText(QString("%1").arg(device_number)); + dynamic_cast(channelWidget->itemWidget(item,3))->setText(QString("Paired")); +} + +void +AddPair::searchTimeout(int channel) +{ + // Kick if off again, just mimic user reselecting the same sensor type + sensorChanged(channel); +} + + +void +AddPair::getChannelValues() +{ + // enable disable widgets based upon sensor selection + for (int i=0; i< channelWidget->invisibleRootItem()->childCount(); i++) { + QTreeWidgetItem *item = channelWidget->invisibleRootItem()->child(i); + + // is it selected or not? + bool enable = (dynamic_cast(channelWidget->itemWidget(item,0))->currentIndex() != 0); + + if (enable) { + QComboBox *p =dynamic_cast(channelWidget->itemWidget(item,0)); + + // speed+cadence is two values! + if (p->itemData(p->currentIndex()) == ANTChannel::CHANNEL_TYPE_SandC) { + dynamic_cast(channelWidget->itemWidget(item,2))->setText(QString("%1 %2") + .arg((int)dynamic_cast(wizard->controller)->myANTlocal->channelValue2(i) //speed + * (appsettings->value(NULL, GC_WHEELSIZE, 2100).toInt()/1000) * 60 / 1000) + .arg((int)dynamic_cast(wizard->controller)->myANTlocal->channelValue(i))); // cad + } else { + dynamic_cast(channelWidget->itemWidget(item,2))->setText(QString("%1") + .arg((int)dynamic_cast(wizard->controller)->myANTlocal->channelValue(i))); + } + } + } + +} + +bool +AddPair::validatePage() +{ + // when next is clicked we need to get the paired values + // and create a profile, a blank profile will be created if + // no devices have been paired. This means devices will be + // automatically paired at runtime + wizard->profile=""; + for (int i=0; i< channelWidget->invisibleRootItem()->childCount(); i++) { + QTreeWidgetItem *item = channelWidget->invisibleRootItem()->child(i); + + // what is it then? unused or restart scan? + QComboBox *p = dynamic_cast(channelWidget->itemWidget(item,0)); + int channel_type = p->itemData(p->currentIndex()).toInt(); + + if (channel_type == ANTChannel::CHANNEL_TYPE_UNUSED) continue; // not paired + + int device_number = dynamic_cast(channelWidget->itemWidget(item,1))->text().toInt(); + + if (device_number) + wizard->profile += QString(wizard->profile != "" ? ", %1%2" : "%1%2") + .arg(device_number) + .arg(ANT::deviceIdCode(channel_type)); + } + return true; +} + +// Final confirmation +AddFinal::AddFinal(AddDeviceWizard *parent) : QWizardPage(parent), wizard(parent) +{ + setTitle(tr("Done")); + setSubTitle(tr("Confirm configuration and add device")); + + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + + QLabel *label = new QLabel("We will now add a new device with the configuration shown " + "below. Please take a moment to review and then click Finish " + "to add the device and complete this wizard, or press the Back " + "button to make amendments.\n\n"); + label->setWordWrap(true); + layout->addWidget(label); + + QHBoxLayout *hlayout = new QHBoxLayout; + layout->addLayout(hlayout); + + QFormLayout *formlayout = new QFormLayout; + formlayout->addRow(new QLabel("Name*", this), (name=new QLineEdit(this))); + formlayout->addRow(new QLabel("Port", this), (port=new QLineEdit(this))); + formlayout->addRow(new QLabel("Profile", this), (profile=new QLineEdit(this))); + formlayout->setFieldGrowthPolicy(QFormLayout::FieldsStayAtSizeHint); + //profile->setFixedWidth(200); + port->setFixedWidth(150); + port->setEnabled(false); // no edit + //name->setFixedWidth(230); + hlayout->addLayout(formlayout); + + QFormLayout *form2layout = new QFormLayout; + form2layout->addRow(new QLabel("Virtual", this), (virtualPower=new QComboBox(this))); + form2layout->addRow(new QLabel("Wheel Size", this), (wheelSize=new QComboBox(this))); + // XXX NOTE: THESE MUST CORRESPOND TO THE CODE + // IN RealtimeController.cpp WHICH + // POST-PROCESSES INBOUND TELEMETRY + virtualPower->addItem("None"); + virtualPower->addItem("Power - Kurt Kinetic Cyclone"); + virtualPower->addItem("Power - Kurt Kinetic Road Machine"); + virtualPower->addItem("Power - Cyclops Fluid 2"); + virtualPower->addItem("Power - BT Advanced Training System"); + virtualPower->addItem("Power - LeMond Revolution"); + virtualPower->addItem("Power - 1UP USA Trainer"); + + wheelSize->addItem("Road/Cross (700C/622)"); // 2100mm + wheelSize->addItem("Tri/TT (650C)"); // 1960mm + wheelSize->addItem("Mountain (26\")"); // 1985mm + wheelSize->addItem("BMX (20\")"); // 1750mm + + hlayout->addLayout(form2layout); + layout->addStretch(); + + selectDefault = new QGroupBox("Selected by default", this); + selectDefault->setCheckable(true); + selectDefault->setChecked(false); + layout->addWidget(selectDefault); + + QGridLayout *grid = new QGridLayout; + selectDefault->setLayout(grid); + grid->addWidget((defWatts=new QCheckBox("Power")), 0,0, Qt::AlignVCenter|Qt::AlignLeft); + grid->addWidget((defBPM=new QCheckBox("Heartrate")), 1,0, Qt::AlignVCenter|Qt::AlignLeft); + grid->addWidget((defKPH=new QCheckBox("Speed")), 0,1, Qt::AlignVCenter|Qt::AlignLeft); + grid->addWidget((defRPM=new QCheckBox("Cadence")), 1,1, Qt::AlignVCenter|Qt::AlignLeft); + layout->addStretch(); +} + +void +AddFinal::initializePage() +{ + port->setText(wizard->portSpec); + profile->setText(wizard->profile); + virtualPower->setCurrentIndex(0); +} + +bool +AddFinal::validatePage() +{ + if (name->text() != "") { + + // lets update 'here' with what we did then... + wizard->here.type = wizard->type; + wizard->here.name = name->text(); + wizard->here.portSpec = port->text(); + wizard->here.deviceProfile = profile->text(); + wizard->here.defaultString = QString(defWatts->isChecked() ? "P" : "") + + QString(defBPM->isChecked() ? "H" : "") + + QString(defRPM->isChecked() ? "C" : "") + + QString(defKPH->isChecked() ? "S" : ""); + wizard->here.postProcess = virtualPower->currentIndex(); + + switch (wheelSize->currentIndex()) { + + default: + case 0: wizard->here.wheelSize = 2100 ; break; + case 1: wizard->here.wheelSize = 1960 ; break; + case 2: wizard->here.wheelSize = 1985 ; break; + case 3: wizard->here.wheelSize = 1750 ; break; + } + + // shut down the controller, if it is there, since it will + // still be connected to the device (in case we hit the back button) + if (wizard->controller) { + wizard->controller->stop(); + sleep(1); + delete wizard->controller; + wizard->controller = NULL; + } + return true; + } + return false; +} diff --git a/src/AddDeviceWizard.h b/src/AddDeviceWizard.h new file mode 100644 index 000000000..b896e1d3b --- /dev/null +++ b/src/AddDeviceWizard.h @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2011 Mark Liversedge (liversedge@gmail.com) + * + * 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 _AddDeviceWizard_h +#define _AddDeviceWizard_h + +#include "GoldenCheetah.h" +#include "MainWindow.h" +#include "DeviceTypes.h" +#include "Serial.h" +#include "RealtimeController.h" +#include "FortiusController.h" +#include "ComputrainerController.h" +#include "ANTlocalController.h" +#include "ANTplusController.h" +#include "ANTChannel.h" +#include "NullController.h" +#include "Settings.h" +#include + +class DeviceScanner; + +class AddDeviceWizard : public QWizard +{ + Q_OBJECT + +public: + AddDeviceWizard(MainWindow *main, DeviceConfiguration &here); + + MainWindow *main; + bool done; // have we finished? + RealtimeController *controller; // for working with devices + + // Put configuration details.. + DeviceConfiguration &here; + + // supported device types + DeviceTypes deviceTypes; + int type; // what are we in the process of adding? + int current; // index into deviceTypes of current + bool found; // we found one! + QString portSpec; // where did we find it? + QString profile; + + DeviceScanner *scanner; + +public slots: + +signals: + +private slots: + +}; + +class AddType : public QWizardPage +{ + Q_OBJECT + + public: + AddType(AddDeviceWizard *); + void initializePage(); + bool validate() const { return false; } + bool isComplete() const { return false; } + int nextId() const { return next; } + + public slots: + void clicked(QString); + + private: + AddDeviceWizard *wizard; + QSignalMapper *mapper; + QLabel *label; + int next; +}; + +class AddSearch : public QWizardPage +{ + Q_OBJECT + + public: + AddSearch(AddDeviceWizard *); + + public slots: + void initializePage(); + int nextId() const; + bool validatePage(); + bool isComplete() const { return wizard->found; } + void doScan(); + void scanFinished(bool); + void cleanupPage(); + void chooseCOMPort(); + + private: + bool isSearching, active; + AddDeviceWizard *wizard; + QProgressBar *bar; + QPushButton *stop; + QComboBox *manual; + QLabel *label, *label1, *label2; + QString specified; +}; + +class AddFirmware : public QWizardPage +{ + Q_OBJECT + + public: + AddFirmware(AddDeviceWizard *); + bool validatePage(); + int nextId() const { return 60; } + + public slots: + void browseClicked(); + + private: + QCheckBox *copy; + QPushButton *ok, *cancel; + QPushButton *browse; + QLabel *help; + QLabel *file; + QLineEdit *name; + MainWindow *mainWindow; + AddDeviceWizard *wizard; +}; + +class AddPair : public QWizardPage +{ + Q_OBJECT + + public: + AddPair(AddDeviceWizard *); + int nextId() const { return 60; } + void initializePage(); + bool validatePage(); + void cleanupPage(); + + public slots: + + void getChannelValues(); + // we found a device on a channel + void channelInfo(int channel, int device_number, int device_id); + // we failed to find a device on the channel + void searchTimeout(int channel); + + // user interactions + void sensorChanged(int channel); // sensor selection changed + private: + AddDeviceWizard *wizard; + QTreeWidget *channelWidget; + QSignalMapper *signalMapper; + QTimer updateValues; +}; + +class AddFinal : public QWizardPage +{ + Q_OBJECT + + public: + AddFinal(AddDeviceWizard *); + void initializePage(); + bool validatePage(); + bool isCommitPage() { return true; } + + private: + AddDeviceWizard *wizard; + + QLineEdit *name; + QComboBox *virtualPower; + QComboBox *wheelSize; + QLineEdit *port; + QLineEdit *profile; + QGroupBox *selectDefault; + QCheckBox *defWatts, *defBPM, *defKPH, *defRPM; + +}; + +class DeviceScanner : public QThread +{ + Q_OBJECT + +signals: + void finished(bool); // threaded scan finished with result x + +public: + DeviceScanner(AddDeviceWizard *); + bool quickScan(bool deep); // non-threaded + void run(); // threaded + void stop(); // stop threaded search + +private: + bool active, found; // is it still looking? + QString portSpec; // did it find a port? + + AddDeviceWizard *wizard; +}; + +#endif // _AddDeviceWizard_h diff --git a/src/Computrainer.cpp b/src/Computrainer.cpp index a9d17b514..c4757a4f8 100644 --- a/src/Computrainer.cpp +++ b/src/Computrainer.cpp @@ -1004,8 +1004,10 @@ int Computrainer::rawRead(uint8_t bytes[], int size) // returns true if the device exists and false if not bool Computrainer::discover(QString filename) { - uint8_t *greeting = (uint8_t *)"Racermate"; + bool returning = false; + uint8_t *greeting = (uint8_t *)"RacerMate"; uint8_t handshake[7]; + int rc; if (filename.isEmpty()) return false; // no null filenames thanks @@ -1016,10 +1018,18 @@ bool Computrainer::discover(QString filename) openPort(); // send a probe - if (rawWrite(greeting, 9) == -1) return false; + if ((rc=rawWrite(greeting, 9)) == -1) { + closePort(); + return false; + } // did we get something back from the device? - if (rawRead(handshake, 6) != 6) return false; + if ((rc=rawRead(handshake, 6)) != 6) { + closePort(); + return false; + } + + closePort(); handshake[6] = '\0'; diff --git a/src/ComputrainerController.cpp b/src/ComputrainerController.cpp index 792b3ee4f..8cc5b0fbb 100644 --- a/src/ComputrainerController.cpp +++ b/src/ComputrainerController.cpp @@ -22,7 +22,7 @@ ComputrainerController::ComputrainerController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) { - myComputrainer = new Computrainer (parent, dc->portSpec); + myComputrainer = new Computrainer (parent, dc ? dc->portSpec : ""); // we may get NULL passed when configuring } @@ -55,7 +55,10 @@ ComputrainerController::stop() bool -ComputrainerController::discover(DeviceConfiguration *) {return false; } // NOT IMPLEMENTED YET +ComputrainerController::discover(QString name) +{ + return myComputrainer->discover(name); // go probe it... +} bool ComputrainerController::doesPush() { return false; } diff --git a/src/ComputrainerController.h b/src/ComputrainerController.h index 29829ae72..bfb4b179c 100644 --- a/src/ComputrainerController.h +++ b/src/ComputrainerController.h @@ -37,7 +37,7 @@ public: int restart(); // restart after paused int pause(); // pauses data collection, inbound telemetry is discarded int stop(); // stops data collection thread - bool discover(DeviceConfiguration *); // tell if a device is present at port passed + bool discover(QString); // tell if a device is present at port passed // telemetry push pull diff --git a/src/ConfigDialog.cpp b/src/ConfigDialog.cpp index 0e1024841..68cf400fc 100644 --- a/src/ConfigDialog.cpp +++ b/src/ConfigDialog.cpp @@ -8,6 +8,8 @@ #include "Settings.h" #include "Zones.h" +#include "AddDeviceWizard.h" + /* cyclist dialog protocol redesign: * no zones: @@ -82,11 +84,8 @@ ConfigDialog::ConfigDialog(QDir _home, Zones *_zones, MainWindow *mainWindow) : fortiusFirmware = appsettings->value(this, FORTIUS_FIRMWARE, "").toString(); connect(closeButton, SIGNAL(clicked()), this, SLOT(accept())); - connect(devicePage->typeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changedType(int))); connect(devicePage->addButton, SIGNAL(clicked()), this, SLOT(devaddClicked())); - connect(devicePage->firmwareButton, SIGNAL(clicked()), this, SLOT(firmwareClicked())); connect(devicePage->delButton, SIGNAL(clicked()), this, SLOT(devdelClicked())); - connect(devicePage->pairButton, SIGNAL(clicked()), this, SLOT(devpairClicked())); connect(contentsWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(changePage(QListWidgetItem *, QListWidgetItem*))); connect(saveButton, SIGNAL(clicked()), this, SLOT(save_Clicked())); @@ -234,82 +233,10 @@ void ConfigDialog::devaddClicked() { DeviceConfiguration add; - DeviceTypes Supported; - - // get values from the gui elements - add.name = devicePage->deviceName->displayText(); - add.type = devicePage->typeSelector->itemData(devicePage->typeSelector->currentIndex()).toInt(); - add.portSpec = devicePage->deviceSpecifier->displayText(); - add.deviceProfile = devicePage->deviceProfile->displayText(); - add.postProcess = devicePage->virtualPower->currentIndex(); - - // NOT IMPLEMENTED IN THIS RELEASE XXX - //add.isDefaultDownload = devicePage->isDefaultDownload->isChecked() ? true : false; - //add.isDefaultRealtime = devicePage->isDefaultDownload->isChecked() ? true : false; - - // Validate the name - QRegExp nameSpec(".+"); - if (nameSpec.exactMatch(add.name) == false) { - QMessageBox::critical(0, "Invalid Device Name", - QString("Device Name should be non-blank")); - return ; + AddDeviceWizard *p = new AddDeviceWizard(mainWindow, add); + if (p->exec() == QDialog::Accepted) { + devicePage->deviceListModel->add(add); } - - // Validate the portSpec - QRegExp antSpec("[^:]*:[^:]*"); // ip:port same as TCP ... for now... - QRegExp tcpSpec("[^:]*:[^:]*"); // ip:port -#ifdef WIN32 - QRegExp serialSpec("COM[0-9]*"); // COMx for WIN32, /dev/* for others -#else - QRegExp serialSpec("/dev/.*"); // COMx for WIN32, /dev/* for others -#endif - - // check the portSpec is valid, based upon the connection type - switch (Supported.getType(add.type).connector) { - case DEV_QUARQ : - if (antSpec.exactMatch(add.portSpec) == false) { - QMessageBox::critical(0, "Invalid Port Specification", - QString("For ANT devices the specifier must be ") + - "hostname:portnumber"); - return ; - } - break; - case DEV_SERIAL : - if (serialSpec.exactMatch(add.portSpec) == false) { - QMessageBox::critical(0, "Invalid Port Specification", - QString("For Serial devices the specifier must be ") + -#ifdef WIN32 - "COMn" -#else - "/dev/xxxxx" -#endif - ); - return ; - } - break; - - case DEV_LIBUSB : - - // Is the fortius firmware configured? - if (add.type == DEV_FORTIUS && fortiusFirmware == "") { - QMessageBox::critical(0, "Fortius Firmware", - QString("For Fortius devices you must import the firmware ") + - "using the Firmware button."); - return ; - } - break; - - case DEV_TCP : - if (tcpSpec.exactMatch(add.portSpec) == false) { - QMessageBox::critical(0, "Invalid Port Specification", - QString("For TCP streaming devices the specifier must be ") + - "hostname:portnumber"); - return ; - } - break; - } - - devicePage->deviceListModel->add(add); } void @@ -321,28 +248,13 @@ ConfigDialog::devdelClicked() void ConfigDialog::devpairClicked() { - DeviceConfiguration add; - - // get values from the gui elements - add.name = devicePage->deviceName->displayText(); - add.type = devicePage->typeSelector->itemData(devicePage->typeSelector->currentIndex()).toInt(); - add.portSpec = devicePage->deviceSpecifier->displayText(); - add.deviceProfile = devicePage->deviceProfile->displayText(); - - QProgressDialog *progress = new QProgressDialog("Looking for Devices...", "Abort Scan", 0, 200, this); - progress->setWindowModality(Qt::WindowModal); - - devicePage->pairClicked(&add, progress); + // replaced by wizard. } void ConfigDialog::firmwareClicked() { - // we need to set the firmware for this device - QString current = fortiusFirmware; - - FortiusDialog *p = new FortiusDialog(mainWindow, fortiusFirmware); - p->exec(); + // replaced by wizard } // diff --git a/src/DeviceConfiguration.cpp b/src/DeviceConfiguration.cpp index d8f39b435..3fe33a793 100644 --- a/src/DeviceConfiguration.cpp +++ b/src/DeviceConfiguration.cpp @@ -31,8 +31,8 @@ DeviceConfiguration::DeviceConfiguration() { // just set all to empty! type=0; - isDefaultDownload=false; - isDefaultRealtime=false; + defaultString=""; + wheelSize=2100; postProcess=0; controller=NULL; } @@ -86,17 +86,17 @@ DeviceConfigurations::readConfig() configVal = appsettings->value(NULL, configStr); Entry.type = configVal.toInt(); + configStr = QString("%1%2").arg(GC_DEV_WHEEL).arg(i+1); + configVal = appsettings->value(NULL, configStr); + Entry.wheelSize = configVal.toInt(); + configStr = QString("%1%2").arg(GC_DEV_PROF).arg(i+1); configVal = appsettings->value(NULL, configStr); Entry.deviceProfile = configVal.toString(); - configStr = QString("%1%2").arg(GC_DEV_DEFI).arg(i+1); + configStr = QString("%1%2").arg(GC_DEV_DEF).arg(i+1); configVal = appsettings->value(NULL, configStr); - Entry.isDefaultDownload = configVal.toInt(); - - configStr = QString("%1%2").arg(GC_DEV_DEFR).arg(i+1); - configVal = appsettings->value(NULL, configStr); - Entry.isDefaultRealtime = configVal.toInt(); + Entry.defaultString = configVal.toString(); configStr = QString("%1%2").arg(GC_DEV_VIRTUAL).arg(i+1); configVal = appsettings->value(NULL, configStr); @@ -124,6 +124,10 @@ DeviceConfigurations::writeConfig(QList Configuration) // type configStr = QString("%1%2").arg(GC_DEV_TYPE).arg(i+1); appsettings->setValue(configStr, Configuration.at(i).type); + + // wheel size + configStr = QString("%1%2").arg(GC_DEV_WHEEL).arg(i+1); + appsettings->setValue(configStr, Configuration.at(i).wheelSize); // portSpec configStr = QString("%1%2").arg(GC_DEV_SPEC).arg(i+1); @@ -133,13 +137,9 @@ DeviceConfigurations::writeConfig(QList Configuration) configStr = QString("%1%2").arg(GC_DEV_PROF).arg(i+1); appsettings->setValue(configStr, Configuration.at(i).deviceProfile); - // isDefaultDownload - configStr = QString("%1%2").arg(GC_DEV_DEFI).arg(i+1); - appsettings->setValue(configStr, Configuration.at(i).isDefaultDownload); - - // isDefaultRealtime - configStr = QString("%1%2").arg(GC_DEV_DEFR).arg(i+1); - appsettings->setValue(configStr, Configuration.at(i).isDefaultRealtime); + // default string + configStr = QString("%1%2").arg(GC_DEV_DEF).arg(i+1); + appsettings->setValue(configStr, Configuration.at(i).defaultString); // virtual post Process... configStr = QString("%1%2").arg(GC_DEV_VIRTUAL).arg(i+1); diff --git a/src/DeviceConfiguration.h b/src/DeviceConfiguration.h index cc7c54a02..b980d125f 100644 --- a/src/DeviceConfiguration.h +++ b/src/DeviceConfiguration.h @@ -37,10 +37,9 @@ class DeviceConfiguration // used by ANT to store ANTIDs // available for use by all devices - bool isDefaultDownload, // not implemented yet - isDefaultRealtime; // not implemented yet - - int postProcess; + QString defaultString; // PHCS for power/heartrate/cadence/speed from this device + int wheelSize; // set wheel size for each device + int postProcess; // virtualChannel RealtimeController *controller; // can be used to allocate controller for this device // although a bit odd, it makes synchronising the config diff --git a/src/DeviceTypes.cpp b/src/DeviceTypes.cpp index e694fe865..1df861a7c 100644 --- a/src/DeviceTypes.cpp +++ b/src/DeviceTypes.cpp @@ -29,21 +29,43 @@ static DeviceType SupportedDevices[] = { #ifdef Q_OS_WIN32 - { DEV_ANTLOCAL, DEV_USB, (char *) "Native ANT+", true, false }, + { DEV_ANTLOCAL, DEV_USB, (char *) "Native ANT+", true, false, + "ANT+ devices such as SRM, Powertap or Quarq power meters, Heart rate belts, " + "speed or cadence meters via a Garmin ANT+ USB1 or USB2 stick", + ":images/devices/garminusb.png" }, #else - { DEV_ANTLOCAL, DEV_SERIAL, (char *) "Native ANT+", true, false }, + { DEV_ANTLOCAL, DEV_SERIAL, (char *) "Native ANT+", true, false, + "ANT+ devices such as SRM, Powertap or Quarq power meters, Heart rate belts, " + "speed or cadence meters via a Garmin ANT+ USB1 or USB2 stick" , + ":images/devices/garminusb.png" }, #endif - { DEV_CT, DEV_SERIAL, (char *) "Racermate Computrainer",true, false }, + { DEV_CT, DEV_SERIAL, (char *) "Racermate Computrainer",true, false, + "Racermate Computrainer Lab or Pro bike trainer with the handlebar controller " + "connected via a USB adaptor or directly connected to a local serial port." , + ":images/devices/computrainer.png" }, #ifdef GC_HAVE_LIBUSB - { DEV_FORTIUS, DEV_LIBUSB, (char *) "Tacx Fortius", true, false }, + { DEV_FORTIUS, DEV_LIBUSB, (char *) "Tacx Fortius", true, false, + "Tacx Fortius/iMagic bike trainer with the handlebar controller connected " + "to a USB port. Please make sure you have device firmware to hand." , + ":images/devices/fortius.png" }, #endif - { 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 }, +#if 0 + { DEV_GSERVER, DEV_TCP, (char *) "Golden Cheetah Server", false, false, + "Golden Cheetah racing server, not curently supported." }, + { DEV_NULL, DEV_TCP, (char *) "Null device (testing)", false, false, + "Testing device used for development only." }. +#endif +#if 0 // deprecated, but keeping code until at least 3.1 + { DEV_ANTPLUS, DEV_QUARQ, (char *) "ANT+ via Quarqd", true, false, + "ANT+ devices such as SRM, Powertap or Quarq power meters, Heart rate belts, " + "speed or cadence meters via an existing Quarqd server" , + ":images/devices/quarqd.png" }, +#endif + // { 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 }, - { 0, 0, NULL, 0, 0 } + { 0, 0, NULL, 0, 0, "", "" } }; DeviceTypes::DeviceTypes() diff --git a/src/DeviceTypes.h b/src/DeviceTypes.h index fb8606f94..4e6f78a73 100644 --- a/src/DeviceTypes.h +++ b/src/DeviceTypes.h @@ -49,6 +49,8 @@ class DeviceType char *name; // narrative name bool realtime; // can it do realtime bool download; // can it do download? + QString description; // tell me about it + QString image; // filename for image }; class DeviceTypes diff --git a/src/Fortius.cpp b/src/Fortius.cpp index 1b03a28c0..45f84ad3a 100644 --- a/src/Fortius.cpp +++ b/src/Fortius.cpp @@ -46,7 +46,7 @@ const static uint8_t slope_command[12] = { /* ---------------------------------------------------------------------- * CONSTRUCTOR/DESRTUCTOR * ---------------------------------------------------------------------- */ -Fortius::Fortius(QObject *parent, QString) : QThread(parent) +Fortius::Fortius(QObject *parent) : QThread(parent) { devicePower = deviceHeartRate = deviceCadence = deviceSpeed = 0.00; @@ -494,6 +494,11 @@ int Fortius::closePort() return 0; } +bool Fortius::find() +{ + return usb2->find(); +} + int Fortius::openPort() { // on windows we try on USB2 then on USB1 then fail... diff --git a/src/Fortius.h b/src/Fortius.h index eb3f52ca9..be1f59dfd 100644 --- a/src/Fortius.h +++ b/src/Fortius.h @@ -72,7 +72,7 @@ class Fortius : public QThread { public: - Fortius(QObject *parent=0, QString deviceFilename=0); // pass device + Fortius(QObject *parent=0); // pass device ~Fortius(); QObject *parent; @@ -83,6 +83,8 @@ public: int pause(); // pauses data collection, inbound telemetry is discarded int stop(); // stops data collection thread int quit(int error); // called by thread before exiting + + bool find(); // either unconfigured or configured device found bool discover(QString deviceFilename); // confirm CT is attached to device // SET diff --git a/src/FortiusController.cpp b/src/FortiusController.cpp index c352fb0c4..8563ad27d 100644 --- a/src/FortiusController.cpp +++ b/src/FortiusController.cpp @@ -22,7 +22,7 @@ FortiusController::FortiusController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) { - myFortius = new Fortius (parent, dc->portSpec); + myFortius = new Fortius (parent); } @@ -53,6 +53,11 @@ FortiusController::stop() return myFortius->stop(); } +bool +FortiusController::find() +{ + return myFortius->find(); // needs to find either unconfigured or configured device +} bool FortiusController::discover(DeviceConfiguration *) {return false; } // NOT IMPLEMENTED YET diff --git a/src/FortiusController.h b/src/FortiusController.h index 5088022db..80c66fed7 100644 --- a/src/FortiusController.h +++ b/src/FortiusController.h @@ -37,6 +37,8 @@ public: int restart(); // restart after paused int pause(); // pauses data collection, inbound telemetry is discarded int stop(); // stops data collection thread + + bool find(); bool discover(DeviceConfiguration *); // tell if a device is present at port passed diff --git a/src/LibUsb.cpp b/src/LibUsb.cpp index be3facdff..d423f3701 100644 --- a/src/LibUsb.cpp +++ b/src/LibUsb.cpp @@ -53,9 +53,6 @@ int LibUsb::open() // Find all connected devices. usb_find_devices(); - readBufSize = 0; - readBufIndex = 0; - switch (type) { // Search USB busses for USB2 ANT+ stick host controllers @@ -76,9 +73,26 @@ int LibUsb::open() return 0; } +bool LibUsb::find() +{ + usb_set_debug(0); + usb_find_busses(); + usb_find_devices(); + + switch (type) { + + // Search USB busses for USB2 ANT+ stick host controllers + default: + case TYPE_ANT: return findAntStick(); + break; + + case TYPE_FORTIUS: return findFortius(); + break; + } +} + void LibUsb::close() { - if (device) { // stop any further write attempts whilst we close down usb_dev_handle *p = device; @@ -109,14 +123,16 @@ int LibUsb::read(char *buf, int bytes) } // No, so partially satisfy by emptying the buffer, then refill the buffer for the rest - if (bufRemain) { + // !!! CHECK bufRemain > 0 rather than non-zero fixes annoying bug with ANT+ + // devices not working again after restart "sometimes" + if (bufRemain > 0) { memcpy(buf, readBuf+readBufIndex, bufRemain); } readBufSize = 0; readBufIndex = 0; - int rc = usb_bulk_read(device, readEndpoint, readBuf, 64, 125); + int rc = usb_bulk_read(device, readEndpoint, readBuf, 64, 10); if (rc < 0) { // don't report timeouts - lots of noise so commented out @@ -161,13 +177,43 @@ int LibUsb::write(char *buf, int bytes) if (rc < 0) { - // don't report timeouts - lots of noise - if (rc != -110) qDebug()<<"usb_interrupt_write Error writing: "<< usb_strerror(); + // Report timeouts - previously we ignored -110 errors. This masked a serious + // problem with write on Linux/Mac, the USB stick needed a reset to avoid + // these error messages, so we DO report them now + // XXX if (rc != -110) qDebug()<<"usb_interrupt_write Error writing: "<< usb_strerror(); + qDebug()<<"usb_interrupt_write Error writing ["<next) { + + + for (dev = bus->devices; dev; dev = dev->next) { + + + if (dev->descriptor.idVendor == FORTIUS_VID && dev->descriptor.idProduct == FORTIUS_INIT_PID) { + found = true; + } + if (dev->descriptor.idVendor == FORTIUS_VID && dev->descriptor.idProduct == FORTIUS_PID) { + found = true; + } + } + } + return found; +} + // Open connection to a Tacx Fortius // // The Fortius handlebar controller is an EZ-USB device. This is an @@ -298,12 +344,46 @@ struct usb_dev_handle* LibUsb::OpenFortius() return NULL; } +bool LibUsb::findAntStick() +{ + struct usb_bus* bus; + struct usb_device* dev; + bool found = false; + 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) { + found = true; + } + } + } + return found; +} + struct usb_dev_handle* LibUsb::OpenAntStick() { struct usb_bus* bus; struct usb_device* dev; struct usb_dev_handle* udev; +// for Mac and Linux we do a bus reset on it first... +#ifndef WIN32 + 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) { + + if ((udev = usb_open(dev))) { + usb_reset(udev); + usb_close(udev); + } + } + } + } +#endif + for (bus = usb_get_busses(); bus; bus = bus->next) { for (dev = bus->devices; dev; dev = dev->next) { diff --git a/src/LibUsb.h b/src/LibUsb.h index 1d6ed2125..ac7b23d92 100644 --- a/src/LibUsb.h +++ b/src/LibUsb.h @@ -57,10 +57,14 @@ public: void close(); int read(char *buf, int bytes); int write(char *buf, int bytes); + bool find(); private: struct usb_dev_handle* OpenAntStick(); struct usb_dev_handle* OpenFortius(); + bool findAntStick(); + bool findFortius(); + struct usb_interface_descriptor* usb_find_interface(struct usb_config_descriptor* config_descriptor); struct usb_dev_handle* device; struct usb_interface_descriptor* intf; diff --git a/src/Pages.cpp b/src/Pages.cpp index 4b93bca2e..a4f50ddc5 100644 --- a/src/Pages.cpp +++ b/src/Pages.cpp @@ -771,7 +771,7 @@ DevicePage::DevicePage(QWidget *parent) : QWidget(parent) QTabWidget *tabs = new QTabWidget(this); QWidget *devs = new QWidget(this); tabs->addTab(devs, tr("Devices")); - QVBoxLayout *devLayout = new QVBoxLayout(devs); + QHBoxLayout *devLayout = new QHBoxLayout(devs); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(tabs); @@ -779,55 +779,10 @@ DevicePage::DevicePage(QWidget *parent) : QWidget(parent) DeviceTypes all; devices = all.getList(); - nameLabel = new QLabel(tr("Device Name"),this); - deviceName = new QLineEdit(tr(""), this); - - typeLabel = new QLabel(tr("Device Type"),this); - typeSelector = new QComboBox(this); - typeSelector->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); - - for (int i=0; i< devices.count(); i++) { - DeviceType cur = devices.at(i); - - // WARNING: cur.type is what is stored in configuration - // do not change this!! - typeSelector->addItem(cur.name, cur.type); - } - - specLabel = new QLabel(tr("Device Port"),this); - specHint = new QLabel(); - profHint = new QLabel(); - deviceSpecifier= new QLineEdit(tr(""), this); - - profLabel = new QLabel(tr("Device Profile"),this); - deviceProfile = new QLineEdit(tr(""), this); - - virtualPowerLabel = new QLabel(tr("Virtual Channel"), this); - virtualPower = new QComboBox(this); - - // XXX NOTE: THESE MUST CORRESPOND TO THE CODE - // IN RealtimeController.cpp WHICH - // POST-PROCESSES INBOUND TELEMETRY - virtualPower->addItem("None"); - virtualPower->addItem("Power - Kurt Kinetic Cyclone"); - virtualPower->addItem("Power - Kurt Kinetic Road Machine"); - virtualPower->addItem("Power - Cyclops Fluid 2"); - virtualPower->addItem("Power - BT Advanced Training System"); - virtualPower->addItem("Power - LeMond Revolution"); - virtualPower->addItem("Power - 1UP USA Trainer"); - virtualPower->setCurrentIndex(0); - -// THIS CODE IS DISABLED FOR THIS RELEASE XXX -// isDefaultDownload = new QCheckBox(tr("Default download device"), this); -// isDefaultRealtime = new QCheckBox(tr("Default realtime device"), this); - addButton = new QPushButton(tr("Add"),this); delButton = new QPushButton(tr("Delete"),this); - pairButton = new QPushButton(tr("Pair"),this); - firmwareButton = new QPushButton(tr("Firmware"),this); deviceList = new QTableView(this); - deviceListModel = new deviceModel(this); // replace standard model with ours @@ -846,67 +801,19 @@ DevicePage::DevicePage(QWidget *parent) : QWidget(parent) deviceList->setColumnWidth(2,130); leftLayout = new QGridLayout(); + leftLayout->addWidget(deviceList); + rightLayout = new QVBoxLayout(); - inLayout = new QGridLayout(); - - leftLayout->addWidget(nameLabel, 0,0); - leftLayout->addWidget(deviceName, 0,2); - //leftLayout->setRowMinimumHeight(1,10); - leftLayout->addWidget(typeLabel, 1,0); - leftLayout->addWidget(typeSelector, 1,2); - QHBoxLayout *squeeze = new QHBoxLayout; - squeeze->addStretch(); - leftLayout->addLayout(squeeze, 1,3); - //leftLayout->setRowMinimumHeight(3,10); - leftLayout->addWidget(specHint, 2,2); - leftLayout->addWidget(specLabel, 3,0); - leftLayout->addWidget(deviceSpecifier, 3,2); - //leftLayout->setRowMinimumHeight(6,10); - leftLayout->addWidget(profHint, 4,2); - leftLayout->addWidget(profLabel, 5,0); - leftLayout->addWidget(deviceProfile, 5,2); - leftLayout->setColumnMinimumWidth(1,10); - leftLayout->addWidget(virtualPowerLabel, 6,0); - leftLayout->addWidget(virtualPower, 6,2); - -// THIS CODE IS DISABLED FOR THIS RELEASE XXX -// leftLayout->addWidget(isDefaultDownload, 6,1); -// leftLayout->addWidget(isDefaultRealtime, 8,1); - -// leftLayout->setRowStretch(0, 2); -// leftLayout->setRowStretch(1, 1); -// leftLayout->setRowStretch(2, 2); -// leftLayout->setRowStretch(3, 1); -// leftLayout->setRowStretch(4, 2); -// leftLayout->setRowStretch(5, 1); -// leftLayout->setRowStretch(6, 2); -// leftLayout->setRowStretch(7, 1); -// leftLayout->setRowStretch(8, 2); - rightLayout->addWidget(addButton); - rightLayout->addSpacing(10); rightLayout->addWidget(delButton); rightLayout->addStretch(); - rightLayout->addWidget(firmwareButton); - rightLayout->addWidget(pairButton); - inLayout->addItem(leftLayout, 0,0); - inLayout->addItem(rightLayout, 0,1); - - devLayout->addLayout(inLayout); - devLayout->addWidget(deviceList); + devLayout->addLayout(leftLayout); + devLayout->addLayout(rightLayout); multiCheck = new QCheckBox("Allow multiple devices in Train View", this); multiCheck->setChecked(appsettings->value(this, TRAIN_MULTI, false).toBool()); - devLayout->addWidget(multiCheck); - - devLayout->setStretch(0,0); - devLayout->setStretch(1,99); - devLayout->setStretch(2,0); - - // to make sure the default checkboxes have been set appropiately... - // THIS CODE IS DISABLED IN THIS RELEASE XXX - // isDefaultRealtime->setEnabled(false); + leftLayout->addWidget(multiCheck); setConfigPane(); } @@ -914,82 +821,7 @@ DevicePage::DevicePage(QWidget *parent) : QWidget(parent) void DevicePage::setConfigPane() { - // depending upon the type of device selected - // the spec hint tells the user the format they should use - DeviceTypes Supported; - - // sorry... ;-) obfuscated c++ contest winner 2009 - 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(); - profLabel->show(); - deviceProfile->show(); - break; - - case DEV_SERIAL: -#ifdef WIN32 - specHint->setText("COMx"); -#else - specHint->setText("/dev/xxxx"); -#endif - 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; - - case DEV_LIBUSB: - specHint->hide(); - specLabel->hide(); - deviceSpecifier->hide(); - profHint->setText("antid 1, antid 2 ..."); - profHint->hide(); - profLabel->hide(); - deviceProfile->hide(); - break; - } - - int type = Supported.getType(typeSelector->itemData(typeSelector->currentIndex()).toInt()).type; - - // pair button only valid for ANT+ (Quarqd or Native) - if (type == DEV_ANTLOCAL || type == DEV_ANTPLUS) { - profHint->show(); - profLabel->show(); - pairButton->show(); - deviceProfile->show(); - } else pairButton->hide(); - - // pair button only valid for ANT+ (Quarqd or Native) - if (type == DEV_FORTIUS) firmwareButton->show(); - else firmwareButton->hide(); + // does nothing for now. } @@ -1040,9 +872,6 @@ deviceModel::del() void DevicePage::pairClicked(DeviceConfiguration *dc, QProgressDialog *progress) { - ANTplusController ANTplus(0, dc); - ANTplus.discover(dc, progress); - deviceProfile->setText(dc->deviceProfile); } deviceModel::deviceModel(QObject *parent) : QAbstractTableModel(parent) diff --git a/src/Pages.h b/src/Pages.h index b69162d2e..082d2dcb2 100644 --- a/src/Pages.h +++ b/src/Pages.h @@ -273,34 +273,10 @@ class DevicePage : public QWidget QList devices; - // GUI Elements - QGroupBox *deviceGroup; - QLabel *nameLabel; - QLineEdit *deviceName; - - QLabel *typeLabel; - QComboBox *typeSelector; - - QLabel *specLabel; - QLabel *specHint; // hints at the format for a port spec - QLabel *profHint; // hints at the format for profile info - QLineEdit *deviceSpecifier; - - QLabel *profLabel; - QLineEdit *deviceProfile; - - QCheckBox *isDefaultDownload; - QCheckBox *isDefaultRealtime; - - QLabel *virtualPowerLabel; // do we compute power using an algorithm? - QComboBox *virtualPower; - QTableView *deviceList; QPushButton *addButton; QPushButton *delButton; - QPushButton *pairButton; - QPushButton *firmwareButton; QGridLayout *leftLayout; QVBoxLayout *rightLayout; diff --git a/src/QuarqdClient.cpp b/src/QuarqdClient.cpp index 3b6f6c95a..9dabe4e7b 100644 --- a/src/QuarqdClient.cpp +++ b/src/QuarqdClient.cpp @@ -46,9 +46,11 @@ QuarqdClient::QuarqdClient(QObject *parent, DeviceConfiguration *devConf) : QThr { Status=0; // server hostname and TCP port# - deviceHostname = devConf->portSpec.section(':',0,0).toAscii(); // after the colon - devicePort = (int)QString(devConf->portSpec).section(':',1,1).toInt(); // after the colon - antIDs = devConf->deviceProfile.split(","); + if (devConf) { + deviceHostname = devConf->portSpec.section(':',0,0).toAscii(); // after the colon + devicePort = (int)QString(devConf->portSpec).section(':',1,1).toInt(); // after the colon + antIDs = devConf->deviceProfile.split(","); + } lastReadWatts = 0; lastReadCadence = 0; lastReadSpeed = 0; diff --git a/src/RealtimeController.cpp b/src/RealtimeController.cpp index 3456bda0e..bcd21cbad 100644 --- a/src/RealtimeController.cpp +++ b/src/RealtimeController.cpp @@ -42,7 +42,8 @@ int RealtimeController::start() { return 0; } int RealtimeController::restart() { return 0; } int RealtimeController::pause() { return 0; } int RealtimeController::stop() { return 0; } -bool RealtimeController::discover(char *) { return false; } +bool RealtimeController::find() { return false; } +bool RealtimeController::discover(QString) { return false; } bool RealtimeController::doesPull() { return false; } bool RealtimeController::doesPush() { return false; } bool RealtimeController::doesLoad() { return false; } diff --git a/src/RealtimeController.h b/src/RealtimeController.h index fe7657ae6..3c11e616d 100644 --- a/src/RealtimeController.h +++ b/src/RealtimeController.h @@ -28,8 +28,10 @@ #define DEVICE_ERROR 1 #define DEVICE_OK 0 -class RealtimeController +class RealtimeController : public QObject { + Q_OBJECT + public: TrainTool *parent; // for push devices @@ -40,7 +42,10 @@ public: virtual int restart(); // restart after paused virtual int pause(); // pauses data collection, inbound telemetry is discarded virtual int stop(); // stops data collection thread - virtual bool discover(char *pathname); // tell if a device is present at port passed + + // for auto-configuration + virtual bool find(); // tell if the device is present (usb typically) + virtual bool discover(QString); // tell if a device is present at serial port passed // push or pull telemetry virtual bool doesPush(); // this device is a push device (e.g. Quarq) diff --git a/src/Serial.cpp b/src/Serial.cpp index 7358b96e8..c53df2e68 100644 --- a/src/Serial.cpp +++ b/src/Serial.cpp @@ -328,8 +328,24 @@ find_devices(char *result[], int capacity) 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/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]+|SLAB_USBtoUART|usbmodem[0-9A-F]+|usbserial-[0-9A-F]+|KeySerial[0-9])|ttyUSB[0-9]|ttyS[0-2])$", + "^(cu\\.(PL2303-[0-9A-F]+|ANTUSBStick.slabvcp|SLAB_USBtoUART|usbmodem[0-9A-F]+|usbserial-[0-9A-F]+|KeySerial[0-9])|ttyUSB[0-9]|ttyS[0-2]|ttyACM*|ttyMI*|rfcomm*)$", REG_EXTENDED|REG_NOSUB)) { assert(0); } diff --git a/src/Settings.h b/src/Settings.h index 5c418949e..d48832cf7 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -108,8 +108,8 @@ #define GC_DEV_SPEC "devicespec" #define GC_DEV_PROF "deviceprof" #define GC_DEV_TYPE "devicetype" -#define GC_DEV_DEFI "devicedefi" -#define GC_DEV_DEFR "devicedefr" +#define GC_DEV_DEF "devicedef" +#define GC_DEV_WHEEL "devicewheel" #define GC_DEV_VIRTUAL "devicepostProcess" // data processor config diff --git a/src/TrainTool.cpp b/src/TrainTool.cpp index a22d2e7a5..92a4eb1f1 100644 --- a/src/TrainTool.cpp +++ b/src/TrainTool.cpp @@ -702,7 +702,7 @@ void TrainTool::Start() // when start button is pressed // and reset connection to device // this appeara to help with ANT USB2 sticks #ifdef GC_HAVE_LIBUSB - usb_init(); + //usb_init(); //XXX lets not its a clusterfuck #endif // if we have selected multiple devices lets diff --git a/src/USBXpress.cpp b/src/USBXpress.cpp index 96a07cbc4..723cb75b0 100644 --- a/src/USBXpress.cpp +++ b/src/USBXpress.cpp @@ -25,6 +25,36 @@ USBXpress::USBXpress() {} // nothing to do - all members are static +bool USBXpress::find() +{ + DWORD numDevices; + + // any USBXpress devices connected? + SI_GetNumDevices(&numDevices); + if (numDevices == 0) return -1; + + // lets see if one of them is a GARMIN USB1 stick and open it + for (unsigned int i=0; i + images/devices/garminusb.png + images/devices/computrainer.png + images/devices/fortius.png + images/devices/quarqd.png images/toolbar/main/togglefull.png images/toolbar/main/hideside.png images/toolbar/main/normal.png diff --git a/src/images/devices/computrainer.png b/src/images/devices/computrainer.png new file mode 100644 index 000000000..a5f227cda Binary files /dev/null and b/src/images/devices/computrainer.png differ diff --git a/src/images/devices/fortius.png b/src/images/devices/fortius.png new file mode 100644 index 000000000..0461f0321 Binary files /dev/null and b/src/images/devices/fortius.png differ diff --git a/src/images/devices/garminusb.png b/src/images/devices/garminusb.png new file mode 100644 index 000000000..4765ff78f Binary files /dev/null and b/src/images/devices/garminusb.png differ diff --git a/src/images/devices/quarqd.png b/src/images/devices/quarqd.png new file mode 100644 index 000000000..592abccf1 Binary files /dev/null and b/src/images/devices/quarqd.png differ diff --git a/src/src.pro b/src/src.pro index b589f9c3d..fe906c324 100644 --- a/src/src.pro +++ b/src/src.pro @@ -178,6 +178,7 @@ HEADERS += ../qxt/src/qxtspanslider.h \ ../qxt/src/qxtstyleoptionscheduleviewitem.h HEADERS += \ + AddDeviceWizard.h \ Aerolab.h \ AerolabWindow.h \ AthleteTool.h \ @@ -349,6 +350,7 @@ LEXSOURCES = JsonRideFile.l WithingsParser.l #QMAKE_YACCFLAGS = -t -d SOURCES += \ + AddDeviceWizard.cpp \ AerobicDecoupling.cpp \ Aerolab.cpp \ AerolabWindow.cpp \