Files
GoldenCheetah/src/AddDeviceWizard.cpp
2013-01-30 22:34:25 +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<50; 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;
}