Files
GoldenCheetah/src/AddDeviceWizard.cpp
Mark Liversedge 96ddf765c3 Kickr connection fail gracefully
.. the code assumes the kickr connection will always work, even
when no BTLE hardware is present.

If no BTLE hardware is available then connections fail immediately
and so do attempts to find devices.

If BTLE hardware is available but the kickr connection cannot be
established in 30 seconds in train view then it returns an error.

Also added better device descriptions to check the device subtype
so we can identify stages, inride and kickr separately.
2013-02-07 09:16:46 +00:00

1190 lines
40 KiB
C++

/*
* 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
// 55. Pair for BTLE
// 60. Finalise
//
// Main wizard
AddDeviceWizard::AddDeviceWizard(MainWindow *main) : QWizard(main), main(main)
{
#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_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;
#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("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 = "";
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("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
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("\nDevice found (%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 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("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();
#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<QComboBox*>(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<ANTlocalController*>(wizard->controller)->setDevice(wizard->portSpec);
dynamic_cast<ANTlocalController*>(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
// wait for it to start
#ifdef WIN32
Sleep(1000);
#else
sleep(1);
#endif
int channels = dynamic_cast<ANTlocalController*>(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<ANTlocalController*>(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; i<channels; i++) sensorChanged(i);
}
void
AddPair::sensorChanged(int channel)
{
QTreeWidgetItem *item = channelWidget->invisibleRootItem()->child(channel);
enableDisable(channelWidget);
// first off lets unassign this channel
dynamic_cast<ANTlocalController*>(wizard->controller)->myANTlocal->setChannel(channel, -1, 0);
dynamic_cast<QLineEdit*>(channelWidget->itemWidget(item,1))->setText("none");
dynamic_cast<QLabel*>(channelWidget->itemWidget(item,2))->setText(0);
// what is it then? unused or restart scan?
QComboBox *p = dynamic_cast<QComboBox *>(channelWidget->itemWidget(item,0));
int channel_type = p->itemData(p->currentIndex()).toInt();
if (channel_type == ANTChannel::CHANNEL_TYPE_UNUSED) {
dynamic_cast<QLabel*>(channelWidget->itemWidget(item,3))->setText("Unused");
} else {
dynamic_cast<QLabel*>(channelWidget->itemWidget(item,3))->setText("Searching...");
dynamic_cast<ANTlocalController*>(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<QLineEdit *>(channelWidget->itemWidget(item,1))->setText(QString("%1").arg(device_number));
dynamic_cast<QLabel *>(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()
{
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<QComboBox*>(channelWidget->itemWidget(item,0))->currentIndex() != 0);
if (enable) {
QComboBox *p =dynamic_cast<QComboBox*>(channelWidget->itemWidget(item,0));
// speed+cadence is two values!
if (p->itemData(p->currentIndex()) == ANTChannel::CHANNEL_TYPE_SandC) {
dynamic_cast<QLabel *>(channelWidget->itemWidget(item,2))->setText(QString("%1 %2")
.arg((int)dynamic_cast<ANTlocalController*>(wizard->controller)->myANTlocal->channelValue2(i) //speed
* (appsettings->value(NULL, GC_WHEELSIZE, 2100).toInt()/1000) * 60 / 1000)
.arg((int)dynamic_cast<ANTlocalController*>(wizard->controller)->myANTlocal->channelValue(i))); // cad
} else {
dynamic_cast<QLabel *>(channelWidget->itemWidget(item,2))->setText(QString("%1")
.arg((int)dynamic_cast<ANTlocalController*>(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<QComboBox *>(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<QLineEdit*>(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:"<<WFApi::getInstance()->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<ANTlocalController*>(wizard->controller)->setDevice(wizard->portSpec);
dynamic_cast<ANTlocalController*>(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
// wait for it to start
#ifdef WIN32
Sleep(1000);
#else
sleep(1);
#endif
int channels = dynamic_cast<ANTlocalController*>(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<ANTlocalController*>(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; i<channels; i++) sensorChanged(i);
}
void
AddPairBTLE::sensorChanged(int channel)
{
QTreeWidgetItem *item = channelWidget->invisibleRootItem()->child(channel);
enableDisable(channelWidget);
// first off lets unassign this channel
dynamic_cast<ANTlocalController*>(wizard->controller)->myANTlocal->setChannel(channel, -1, 0);
dynamic_cast<QLineEdit*>(channelWidget->itemWidget(item,1))->setText("none");
dynamic_cast<QLabel*>(channelWidget->itemWidget(item,2))->setText(0);
// what is it then? unused or restart scan?
QComboBox *p = dynamic_cast<QComboBox *>(channelWidget->itemWidget(item,0));
int channel_type = p->itemData(p->currentIndex()).toInt();
if (channel_type == ANTChannel::CHANNEL_TYPE_UNUSED) {
dynamic_cast<QLabel*>(channelWidget->itemWidget(item,3))->setText("Unused");
} else {
dynamic_cast<QLabel*>(channelWidget->itemWidget(item,3))->setText("Searching...");
dynamic_cast<ANTlocalController*>(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<QLineEdit *>(channelWidget->itemWidget(item,1))->setText(QString("%1").arg(device_number));
dynamic_cast<QLabel *>(channelWidget->itemWidget(item,3))->setText(QString("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<QComboBox*>(channelWidget->itemWidget(item,0))->currentIndex() != 0);
if (enable) {
QComboBox *p =dynamic_cast<QComboBox*>(channelWidget->itemWidget(item,0));
// speed+cadence is two values!
if (p->itemData(p->currentIndex()) == ANTChannel::CHANNEL_TYPE_SandC) {
dynamic_cast<QLabel *>(channelWidget->itemWidget(item,2))->setText(QString("%1 %2")
.arg((int)dynamic_cast<ANTlocalController*>(wizard->controller)->myANTlocal->channelValue2(i) //speed
* (appsettings->value(NULL, GC_WHEELSIZE, 2100).toInt()/1000) * 60 / 1000)
.arg((int)dynamic_cast<ANTlocalController*>(wizard->controller)->myANTlocal->channelValue(i))); // cad
} else {
dynamic_cast<QLabel *>(channelWidget->itemWidget(item,2))->setText(QString("%1")
.arg((int)dynamic_cast<ANTlocalController*>(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<QComboBox *>(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<QLineEdit*>(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");
virtualPower->addItem("Power - Minoura V100 Trainer (H)");
virtualPower->addItem("Power - Minoura V100 Trainer (5)");
virtualPower->addItem("Power - Minoura V100 Trainer (4)");
virtualPower->addItem("Power - Minoura V100 Trainer (3)");
virtualPower->addItem("Power - Minoura V100 Trainer (2)");
virtualPower->addItem("Power - Minoura V100 Trainer (1)");
virtualPower->addItem("Power - Minoura V100 Trainer (L)");
virtualPower->addItem("Power - Saris Powerbeam Pro");
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() != "") {
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<DeviceConfiguration> list = all.getList();
list.insert(0, add);
// call device add wizard.
all.writeConfig(list);
// tell everyone
wizard->main->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;
}