/* * 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" #include "MainWindow.h" #include "Athlete.h" #include "Context.h" // WIZARD FLOW // // 10. Select Device Type // 20. Scan for Device / select Serial // 30. Firmware for Fortius // 50. Pair for ANT // 55. Pair for BTLE // 60. Finalise // // Main wizard AddDeviceWizard::AddDeviceWizard(Context *context) : QWizard(context->mainWindow), context(context) { #ifdef Q_OS_MAC setWizardStyle(QWizard::ModernStyle); #endif // delete when done setWindowModality(Qt::NonModal); // avoid blocking WFAPI calls for kickr setAttribute(Qt::WA_DeleteOnClose); setFixedHeight(530); 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)); // done setPage(55, new AddPairBTLE(this)); // done 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++; } // we don't do a quick scan for a kickr since it takes 15 seconds // to timeout and we don't want to get stuck on the front page for // that long -- it will seem like it has not worked / crashed if (wizard->deviceTypes.Supported[wizard->current].connector != DEV_BTLE) wizard->found = wizard->scanner->quickScan(false); // do a quick scan else wizard->found = false; // 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_BT40 : next = 55; break; case DEV_ANTLOCAL : next = 50; break; // pair default: case DEV_KICKR : 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; #ifdef GC_HAVE_WFAPI void *pool; // get an autorelease pool for this thread! if (wizard->deviceTypes.Supported[wizard->current].connector == DEV_BTLE) pool = WFApi::getInstance()->getPool(); #endif for (int i=0; active && !result && i<10; i++) { // search for longer // better to wait a while, esp. if its just a USB device #ifdef WIN32 Sleep(1000); #else sleep(1); #endif result = quickScan(false); } if (active) emit finished(result); // only signal if we weren't aborted! #ifdef GC_HAVE_WFAPI WFApi::getInstance()->freePool(pool); #endif } 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_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; #ifdef GC_HAVE_WFAPI case DEV_KICKR : wizard->controller = new KickrController(NULL, NULL); break; case DEV_BT40 : wizard->controller = new BT40Controller(NULL, NULL); break; #endif 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)) { // check if controller still exists. gets deleted when scan cancelled if (wizard->controller && 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); #ifdef GC_HAVE_WFAPI // save away the device UUID, so we can choose it when connecting. if (isfound && wizard->deviceTypes.Supported[wizard->current].connector == DEV_BTLE) wizard->portSpec = ((KickrController*)(wizard->controller))->id(); #endif 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(tr("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(tr("Search"), this); hlayout2->addStretch(); hlayout2->addWidget(stop); layout->addLayout(hlayout2); label1 = new QLabel(tr("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(tr("\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 = ""; 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 } void AddSearch::initializePage() { 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(tr("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(tr("Search Again")); if (result == true) { // woohoo we found one if (wizard->deviceTypes.Supported[wizard->current].type == DEV_BT40) { // ok we've started finding devices, lets go straight into the // pair screen since we now want to see the data etc. wizard->next(); } else { bar->hide(); stop->hide(); manual->hide(); label->hide(); label1->hide(); if (wizard->portSpec != "") label2->setText(QString(tr("\nDevice found (%1).\nPress Next to Continue\n")).arg(wizard->portSpec)); else label2->setText(tr("\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(tr("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(tr("Search again")); wizard->scanner->stop(); } } int AddSearch::nextId() const { // Still no dice. Go to the not found dialog if (wizard->found == false) return 60; else { switch(wizard->deviceTypes.Supported[wizard->current].type) { case DEV_ANTLOCAL : return 50; break; // pair case DEV_BT40 : return 55; break; // pair BT devices default: case DEV_KICKR : 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) { // give it time to stop... #ifdef WIN32 Sleep(2000); #else sleep(2); #endif } 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(tr("Browse"), this); copy = new QCheckBox(tr("Copy to Library")); copy->setChecked(true); help = new QLabel(this); help->setWordWrap(true); help->setText(tr("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(tr("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() { // changed to true to support 1932 version which doesn't require a firmware file QString filePath = name->text(); if (filePath == "" || !QFile(filePath).exists()) return true; // either copy it, or reference it! if (copy->isChecked()) { QString fileName = QFileInfo(filePath).fileName(); QString targetFileName = QFileInfo(context->athlete->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].user) { 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(); #ifdef WIN32 Sleep(1000); #else sleep(1); #endif 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); 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 // wait for it to start #ifdef WIN32 Sleep(1000); #else sleep(1); #endif 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(tr("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(tr("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(tr("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(tr("Unused")); } else { dynamic_cast(channelWidget->itemWidget(item,3))->setText(tr("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(tr("Paired"))); } void AddPair::searchTimeout(int channel) { // Kick if off again, just mimic user reselecting the same sensor type sensorChanged(channel); } void AddPair::getChannelValues() { if (wizard->controller == NULL) return; // 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; } // Pair devices AddPairBTLE::AddPairBTLE(AddDeviceWizard *parent) : QWizardPage(parent), wizard(parent) { setTitle(tr("Pair Devices")); setSubTitle(tr("Search for and pair Bluetooth 4.0 devices")); signalMapper = NULL; QVBoxLayout *layout = new QVBoxLayout; setLayout(layout); channelWidget = new QTreeWidget(this); layout->addWidget(channelWidget); } void AddPairBTLE::cleanupPage() { updateValues.stop(); if (wizard->controller) { wizard->controller->stop(); #ifdef WIN32 Sleep(1000); #else sleep(1); #endif delete wizard->controller; wizard->controller = NULL; } } void AddPairBTLE::initializePage() { #ifdef GC_HAVE_WFAPI qDebug()<<"found this many devices:"<deviceCount(); #endif // 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); 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 // wait for it to start #ifdef WIN32 Sleep(1000); #else sleep(1); #endif 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(tr("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(tr("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(tr("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(tr("Unused")); } else { dynamic_cast(channelWidget->itemWidget(item,3))->setText(tr("Searching...")); dynamic_cast(wizard->controller)->myANTlocal->setChannel(channel, 0, channel_type); } } void AddPairBTLE::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(tr("Paired"))); } void AddPairBTLE::searchTimeout(int channel) { // Kick if off again, just mimic user reselecting the same sensor type sensorChanged(channel); } void AddPairBTLE::getChannelValues() { if (wizard->controller == NULL) return; // 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 AddPairBTLE::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(tr("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(tr("Name*"), this), (name=new QLineEdit(this))); formlayout->addRow(new QLabel(tr("Port"), this), (port=new QLineEdit(this))); formlayout->addRow(new QLabel(tr("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(tr("Virtual"), this), (virtualPower=new QComboBox(this))); form2layout->addRow(new QLabel(tr("Wheel Size"), this), (wheelSize=new QComboBox(this))); // NOTE: These must correspond to the code in RealtimeController.cpp that // post-processes inbound telemetry. virtualPower->addItem(tr("None")); virtualPower->addItem(tr("Power - Kurt Kinetic Cyclone")); virtualPower->addItem(tr("Power - Kurt Kinetic Road Machine")); virtualPower->addItem(tr("Power - Cyclops Fluid 2")); virtualPower->addItem(tr("Power - BT Advanced Training System")); virtualPower->addItem(tr("Power - LeMond Revolution")); virtualPower->addItem(tr("Power - 1UP USA Trainer")); virtualPower->addItem(tr("Power - Minoura V100 Trainer (H)")); virtualPower->addItem(tr("Power - Minoura V100 Trainer (5)")); virtualPower->addItem(tr("Power - Minoura V100 Trainer (4)")); virtualPower->addItem(tr("Power - Minoura V100 Trainer (3)")); virtualPower->addItem(tr("Power - Minoura V100 Trainer (2)")); virtualPower->addItem(tr("Power - Minoura V100 Trainer (1)")); virtualPower->addItem(tr("Power - Minoura V100 Trainer (L)")); virtualPower->addItem(tr("Power - Saris Powerbeam Pro")); virtualPower->addItem(tr("Power - Tacx Satori (2)")); virtualPower->addItem(tr("Power - Tacx Satori (4)")); virtualPower->addItem(tr("Power - Tacx Satori (6)")); virtualPower->addItem(tr("Power - Tacx Satori (8)")); virtualPower->addItem(tr("Power - Tacx Satori (10)")); virtualPower->addItem(tr("Power - Tacx Flow (0)")); virtualPower->addItem(tr("Power - Tacx Flow (2)")); virtualPower->addItem(tr("Power - Tacx Flow (4)")); virtualPower->addItem(tr("Power - Tacx Flow (6)")); virtualPower->addItem(tr("Power - Tacx Flow (8)")); virtualPower->addItem(tr("Power - Tacx Blue Twist (1)")); virtualPower->addItem(tr("Power - Tacx Blue Twist (3)")); virtualPower->addItem(tr("Power - Tacx Blue Twist (5)")); virtualPower->addItem(tr("Power - Tacx Blue Twist (7)")); virtualPower->addItem(tr("Power - Tacx Blue Motion (2)")); virtualPower->addItem(tr("Power - Tacx Blue Motion (4)")); virtualPower->addItem(tr("Power - Tacx Blue Motion (6)")); virtualPower->addItem(tr("Power - Tacx Blue Motion (8)")); virtualPower->addItem(tr("Power - Tacx Blue Motion (10)")); virtualPower->addItem(tr("Power - Elite Supercrono Powermag (1)")); virtualPower->addItem(tr("Power - Elite Supercrono Powermag (2)")); virtualPower->addItem(tr("Power - Elite Supercrono Powermag (3)")); virtualPower->addItem(tr("Power - Elite Supercrono Powermag (4)")); virtualPower->addItem(tr("Power - Elite Supercrono Powermag (5)")); virtualPower->addItem(tr("Power - Elite Supercrono Powermag (6)")); virtualPower->addItem(tr("Power - Elite Supercrono Powermag (7)")); virtualPower->addItem(tr("Power - Elite Supercrono Powermag (8)")); virtualPower->addItem(tr("Power - Elite Qubo Power Fluid")); wheelSize->addItem(tr("Road/Cross (700C/622)")); // 2100mm wheelSize->addItem(tr("Tri/TT (650C)")); // 1960mm wheelSize->addItem(tr("Mountain (26\")")); // 1985mm wheelSize->addItem(tr("BMX (20\")")); // 1750mm hlayout->addLayout(form2layout); layout->addStretch(); selectDefault = new QGroupBox(tr("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(tr("Power"))), 0,0, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget((defBPM=new QCheckBox(tr("Heartrate"))), 1,0, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget((defKPH=new QCheckBox(tr("Speed"))), 0,1, Qt::AlignVCenter|Qt::AlignLeft); grid->addWidget((defRPM=new QCheckBox(tr("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() != "") { DeviceConfigurations all; DeviceConfiguration add; // lets update 'here' with what we did then... add.type = wizard->type; add.name = name->text(); add.portSpec = port->text(); add.deviceProfile = profile->text(); add.defaultString = QString(defWatts->isChecked() ? "P" : "") + QString(defBPM->isChecked() ? "H" : "") + QString(defRPM->isChecked() ? "C" : "") + QString(defKPH->isChecked() ? "S" : ""); add.postProcess = virtualPower->currentIndex(); switch (wheelSize->currentIndex()) { default: case 0: add.wheelSize = 2100 ; break; case 1: add.wheelSize = 1960 ; break; case 2: add.wheelSize = 1985 ; break; case 3: add.wheelSize = 1750 ; break; } QList list = all.getList(); list.insert(0, add); // call device add wizard. all.writeConfig(list); // tell everyone wizard->context->notifyConfigChanged(); // 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(); #ifdef WIN32 Sleep(1000); #else sleep(1); #endif delete wizard->controller; wizard->controller = NULL; } return true; } return false; }