From 52b896ecf2476934f155bb2cf7324fa5e19b1ff5 Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Sun, 20 Jan 2013 19:11:16 +0000 Subject: [PATCH] Kickr Skeleton Code Added Skeleton code for device discovery, config and runtime. Now need to develop the WFApi class to support each function. --- src/AddDeviceWizard.cpp | 2 + src/AddDeviceWizard.h | 3 + src/DeviceTypes.cpp | 5 ++ src/DeviceTypes.h | 2 + src/Kickr.cpp | 186 ++++++++++++++++++++++++++++++++++++++++ src/Kickr.h | 82 ++++++++++++++++++ src/KickrController.cpp | 105 +++++++++++++++++++++++ src/KickrController.h | 59 +++++++++++++ src/MainWindow.cpp | 4 + src/TrainTool.cpp | 5 ++ src/WFApi.h | 29 +++++++ src/WFApi.mm | 6 +- src/src.pro | 3 +- 13 files changed, 489 insertions(+), 2 deletions(-) create mode 100644 src/Kickr.cpp create mode 100644 src/Kickr.h create mode 100644 src/KickrController.cpp create mode 100644 src/KickrController.h diff --git a/src/AddDeviceWizard.cpp b/src/AddDeviceWizard.cpp index 8f0f935be..61bdb7229 100644 --- a/src/AddDeviceWizard.cpp +++ b/src/AddDeviceWizard.cpp @@ -124,6 +124,7 @@ AddType::clicked(QString p) if (wizard->found == false) next =20; else { switch(wizard->deviceTypes.Supported[wizard->current].type) { + case DEV_KICKR : case DEV_ANTLOCAL : next = 50; break; // pair default: case DEV_CT : next = 60; break; // confirm and add @@ -181,6 +182,7 @@ DeviceScanner::quickScan(bool deep) // scan quickly or if true scan forever, as #endif case DEV_NULL : wizard->controller = new NullController(NULL, NULL); break; case DEV_ANTLOCAL : wizard->controller = new ANTlocalController(NULL, NULL); break; + case DEV_KICKR : wizard->controller = new KickrController(NULL, NULL); break; default: wizard->controller = NULL; break; diff --git a/src/AddDeviceWizard.h b/src/AddDeviceWizard.h index 767b87067..4f40253b3 100644 --- a/src/AddDeviceWizard.h +++ b/src/AddDeviceWizard.h @@ -24,6 +24,9 @@ #include "DeviceTypes.h" #include "Serial.h" #include "RealtimeController.h" +#ifdef GC_HAVE_WFAPI +#include "KickrController.h" +#endif #ifdef GC_HAVE_LIBUSB #include "FortiusController.h" #endif diff --git a/src/DeviceTypes.cpp b/src/DeviceTypes.cpp index e55d1a563..7cbe95a5f 100644 --- a/src/DeviceTypes.cpp +++ b/src/DeviceTypes.cpp @@ -49,6 +49,11 @@ static DeviceType SupportedDevices[] = "to a USB port. Please make sure you have device firmware to hand." , ":images/devices/fortius.png" }, #endif +#ifdef GC_HAVE_WFAPI + { DEV_KICKR, DEV_BTLE, (char *) "Wahoo Kickr", true, false, + "The Wahoo Fitness Kickr cyling trainer via its Bluetooth smart interface. ", + ":images/devices/kickr.png" }, +#endif #ifdef GC_WANT_ROBOT { DEV_NULL, DEV_TCP, (char *) "Robot", false, false, "Testing device used for development only. If an ERG file is selected it will " diff --git a/src/DeviceTypes.h b/src/DeviceTypes.h index 4e6f78a73..db03b2ec8 100644 --- a/src/DeviceTypes.h +++ b/src/DeviceTypes.h @@ -34,12 +34,14 @@ #define DEV_GSERVER 0x0100 // NOT IMPLEMENTED IN THIS RELEASE XXX #define DEV_GCLIENT 0x0200 // NOT IMPLEMENTED IN THIS RELEASE XXX #define DEV_FORTIUS 0x0800 // Tacx Fortius +#define DEV_KICKR 0x1000 // Wahoo Kickr #define DEV_QUARQ 0x01 // ants use id:hostname:port #define DEV_SERIAL 0x02 // use filename COMx or /dev/cuxxxx #define DEV_TCP 0x03 // tcp port is hostname:port NOT IMPLEMENTED IN THIS RELEASE #define DEV_USB 0x04 // use filename COMx or /dev/cuxxxx #define DEV_LIBUSB 0x08 // will interact directly (i.e. no device file needed) +#define DEV_BTLE 0x10 // bluetooth class DeviceType { diff --git a/src/Kickr.cpp b/src/Kickr.cpp new file mode 100644 index 000000000..7368a9adb --- /dev/null +++ b/src/Kickr.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2013 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 "Kickr.h" + +/* ---------------------------------------------------------------------- + * CONSTRUCTOR/DESRTUCTOR + * ---------------------------------------------------------------------- */ +Kickr::Kickr(QObject *parent, DeviceConfiguration *devConf) : QThread(parent) +{ + this->parent = parent; + this->devConf = devConf; +} + +Kickr::~Kickr() +{ +} + +/* ---------------------------------------------------------------------- + * SET + * ---------------------------------------------------------------------- */ +void Kickr::setDevice(QString) +{ + // not required +} + +void Kickr::setMode(int mode, double load, double gradient) +{ + pvars.lock(); + this->mode = mode; + this->load = load; + this->gradient = gradient; + pvars.unlock(); +} + +void Kickr::setLoad(double load) +{ + pvars.lock(); + if (load > 1500) load = 1500; + if (load < 50) load = 50; + this->load = load; + pvars.unlock(); +} + +void Kickr::setGradient(double gradient) +{ + pvars.lock(); + this->gradient = gradient; + pvars.unlock(); +} + + +/* ---------------------------------------------------------------------- + * GET + * ---------------------------------------------------------------------- */ + +int Kickr::getMode() +{ + int tmp; + pvars.lock(); + tmp = mode; + pvars.unlock(); + return tmp; +} + +double Kickr::getLoad() +{ + double tmp; + pvars.lock(); + tmp = load; + pvars.unlock(); + return tmp; +} + +double Kickr::getGradient() +{ + double tmp; + pvars.lock(); + tmp = gradient; + pvars.unlock(); + return tmp; +} + +void +Kickr::getRealtimeData(RealtimeData &rtData) +{ + rtData.setWatts(0); // XXX watts only... +} + +int +Kickr::start() +{ + QThread::start(); + return 0; +} + +// does nothing - neither does pause +int Kickr::restart() { return 0; } +int Kickr::pause() { return 0; } + +int Kickr::stop() +{ + running = false; + return 0; +} + +// used by thread to set variables and emit event if needed +// on unexpected exit +int Kickr::quit(int code) +{ + // event code goes here! + exit(code); + return 0; // never gets here obviously but shuts up the compiler! +} + +/*---------------------------------------------------------------------- + * THREADED CODE - READS TELEMETRY AND SENDS COMMANDS TO KEEP CT ALIVE + *----------------------------------------------------------------------*/ +void Kickr::run() +{ + // Connect to the device + if (connect()) { + quit(2); + return; // open failed! + } + + running = true; + while(running) { + + // send load + //XXX todo + + msleep(10); + } + + disconnect(); + + quit(0); +} + +bool +Kickr::find() +{ + return false; +} + +int +Kickr::connect() +{ + // connect + + //failed + connected = false; + return -1; +} + +int +Kickr::disconnect() +{ + // disconnect + + connected = false; + return 0; +} + +// check to see of there is a port at the device specified +// returns true if the device exists and false if not +bool Kickr::discover(QString) +{ + return false; +} diff --git a/src/Kickr.h b/src/Kickr.h new file mode 100644 index 000000000..b75d6987a --- /dev/null +++ b/src/Kickr.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2013 Mark Liversedge (liversedge@gmail.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GC_Kickr_h +#define _GC_Kickr_h 1 +#include "GoldenCheetah.h" + +#include +#include +#include +#include +#include +#include "RealtimeController.h" +#include "DeviceConfiguration.h" + +class Kickr : public QThread +{ + +public: + Kickr(QObject *parent=0, DeviceConfiguration * devConf=0); // pass device + ~Kickr(); + + QObject *parent; + + // HIGH-LEVEL FUNCTIONS + int start(); // Calls QThread to start + int restart(); // restart after paused + int pause(); // pauses data collection, inbound telemetry is discarded + int stop(); // stops data collection thread + int quit(int error); // called by thread before exiting + bool discover(QString deviceFilename); // confirm CT is attached to device + + // SET + void setDevice(QString deviceFilename); // setup the device filename + void setLoad(double load); // set the load to generate in ERGOMODE + void setGradient(double gradient); // set the load to generate in SSMODE + void setMode(int mode, + double load=100, // set mode to CT_ERGOMODE or CT_SSMODE + double gradient=1); + + bool find(); + int connect(); + int disconnect(); + + int getMode(); + double getGradient(); + double getLoad(); + void getRealtimeData(RealtimeData &rtData); + +private: + void run(); // called by start to kick off the CT comtrol thread + + // device configuration + DeviceConfiguration *devConf; + + // Mutex for controlling accessing private data + QMutex pvars; + + bool running, connected; + volatile int mode; + volatile double load; + volatile double gradient; + +}; + +#endif // _GC_Kickr_h + diff --git a/src/KickrController.cpp b/src/KickrController.cpp new file mode 100644 index 000000000..c7a2a0569 --- /dev/null +++ b/src/KickrController.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2009 Mark Rages + * Copyright (c) 2009 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 +#include "KickrController.h" +#include "ANT.h" +#include "ANTLogger.h" +#include "RealtimeData.h" + +KickrController::KickrController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) +{ + myKickr = new Kickr(parent, dc); +} + +void +KickrController::setDevice(QString) +{ + // not required +} + +int +KickrController::start() +{ + myKickr->start(); + return 0; +} + + +int +KickrController::restart() +{ + return myKickr->restart(); +} + + +int +KickrController::pause() +{ + return myKickr->pause(); +} + + +int +KickrController::stop() +{ + return myKickr->stop(); +} + +bool +KickrController::find() +{ + return myKickr->find(); +} + +bool +KickrController::discover(QString name) +{ + return myKickr->discover(name); +} + + +bool KickrController::doesPush() { return false; } +bool KickrController::doesPull() { return true; } +bool KickrController::doesLoad() { return true; } + +/* + * gets called from the GUI to get updated telemetry. + * so whilst we are at it we check button status too and + * act accordingly. + * + */ +void +KickrController::getRealtimeData(RealtimeData &rtData) +{ + if(!myKickr->isRunning()) + { + QMessageBox msgBox; + msgBox.setText("Cannot Connect to Kickr"); + msgBox.setIcon(QMessageBox::Critical); + msgBox.exec(); + parent->Stop(1); + return; + } + // get latest telemetry + myKickr->getRealtimeData(rtData); + processRealtimeData(rtData); +} + +void KickrController::pushRealtimeData(RealtimeData &) { } // update realtime data with current values diff --git a/src/KickrController.h b/src/KickrController.h new file mode 100644 index 000000000..654944500 --- /dev/null +++ b/src/KickrController.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2011 Mark Liversedge (liversedge@gmail.com) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "GoldenCheetah.h" + +#include "RealtimeController.h" +#include "DeviceConfiguration.h" +#include "ConfigDialog.h" + +#include "Kickr.h" + +#ifndef _GC_KickrController_h +#define _GC_KickrController_h 1 + +class KickrController : public RealtimeController +{ + Q_OBJECT + +public: + KickrController (TrainTool *parent =0, DeviceConfiguration *dc =0); + + Kickr *myKickr; // the device itself + + int start(); + int restart(); // restart after paused + int pause(); // pauses data collection, inbound telemetry is discarded + int stop(); // stops data collection thread + + bool find(); + bool discover(QString name); + void setDevice(QString); + + // telemetry push pull + bool doesPush(), doesPull(), doesLoad(); + void getRealtimeData(RealtimeData &rtData); + void pushRealtimeData(RealtimeData &rtData); + void setLoad(double) { return; } + +signals: + +private: + +}; + +#endif // _GC_KickrController_h diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 14e39d68e..f9d58201f 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -122,6 +122,8 @@ #include "LibraryParser.h" #include "TrainDB.h" +#include "WFApi.h" + QList mainwindows; // keep track of all the MainWindows we have open QDesktopWidget *desktop = NULL; @@ -130,6 +132,8 @@ MainWindow::MainWindow(const QDir &home) : zones_(new Zones), hrzones_(new HrZones), ride(NULL), workout(NULL) { + qDebug()<<"currentstate="<currentState(); + WFApi::getInstance()->enableBTLE(true, true); if (desktop == NULL) desktop = QApplication::desktop(); static const QIcon hideIcon(":images/toolbar/main/hideside.png"); diff --git a/src/TrainTool.cpp b/src/TrainTool.cpp index 260c0a403..faead0ed7 100644 --- a/src/TrainTool.cpp +++ b/src/TrainTool.cpp @@ -34,6 +34,9 @@ #include "ANTplusController.h" #include "ANTlocalController.h" #include "NullController.h" +#ifdef GC_HAVE_WFAPI +#include "KickrController.h" +#endif #ifdef GC_HAVE_LIBUSB #include "FortiusController.h" #endif @@ -457,6 +460,8 @@ TrainTool::configChanged() Devices[i].controller = new NullController(this, &Devices[i]); } else if (Devices.at(i).type == DEV_ANTLOCAL) { Devices[i].controller = new ANTlocalController(this, &Devices[i]); + } else if (Devices.at(i).type == DEV_KICKR) { + Devices[i].controller = new KickrController(this, &Devices[i]); } } } diff --git a/src/WFApi.h b/src/WFApi.h index 19817be43..53fd69508 100644 --- a/src/WFApi.h +++ b/src/WFApi.h @@ -61,9 +61,15 @@ public: bool enableBTLE(bool enable, bool bondingmode); bool isCommunicationHWReady(); + // current state + int currentState(); + // scan bool discoverDevicesOfType(int eSensorType, int eNetworkType, int timeout); +signals: + void currentStateChanged(int); // hardware conncector state changed + public slots: void stateChanged(); void connectedSensor(void*); @@ -90,4 +96,27 @@ public: }; +// +// We need to define the constants from the WF API headers because they +// assume you are building with OBJ C. And fail to compile with gcc/g++ +// + +// Hardware Connector states +typedef enum +{ + /** The fisica device is not physically connected to the iPod. */ + WFAPI_HWCONN_STATE_NOT_CONNECTED = 0, + /** The fisica device is physically connected to the iPod. */ + WFAPI_HWCONN_STATE_CONNECTED = 0x01, + /** The fisica device is connected and communication is established (norml operating mode). */ + WFAPI_HWCONN_STATE_ACTIVE = 0x02, + /** The fisica device is performing a reset operation. */ + WFAPI_HWCONN_STATE_RESET = 0x04, + /** The BTLE hardware is enabled. */ + WFAPI_HWCONN_STATE_BT40_ENABLED = 0x08, + /** The BTLE controller is in bonding mode. */ + WFAPI_HWCONN_STATE_BT_BONDING_MODE = 0x10, + +} WFConnState; + #endif diff --git a/src/WFApi.mm b/src/WFApi.mm index 09987c1a4..a53ad1407 100644 --- a/src/WFApi.mm +++ b/src/WFApi.mm @@ -90,6 +90,8 @@ static QString toQString(const NSString *nsstr) // ready to scan -(BOOL)isCommunicationHWReady { return [[WFHardwareConnector sharedConnector] isCommunicationHWReady]; } +// current State +-(int)currentState { return [[WFHardwareConnector sharedConnector] currentState]; } // scan -(BOOL)discoverDevicesOfType:(WFSensorType_t)eSensorType onNetwork:(WFNetworkType_t)eNetworkType searchTimeout:(NSTimeInterval)timeout { @@ -173,6 +175,7 @@ bool WFApi::isCommunicationHWReady() { return [wf isCommunicationHWReady]; } bool WFApi::enableBTLE(bool enable, bool bondingmode) { return [wf enableBTLE:enable inBondingMode:bondingmode]; } +int WFApi::currentState() { return [wf currentState]; } bool WFApi::discoverDevicesOfType(int eSensorType, int eNetworkType, int timeout) @@ -218,5 +221,6 @@ qDebug()<<"hasFormware..."; void WFApi::stateChanged() { -qDebug()<<"state changed..."; +qDebug()<<"state changed..." << currentState(); +emit currentStateChanged(currentState()); } diff --git a/src/src.pro b/src/src.pro index cdb9bc65e..5d7f8e107 100644 --- a/src/src.pro +++ b/src/src.pro @@ -167,7 +167,8 @@ macx { # At present this only works on Mac -- since support for # BTLE on Linux and Windows is emerging and there is no # Linux or Windows support for the WF API from Wahoo (yet) - HEADERS += WFApi.h + HEADERS += WFApi.h Kickr.h KickrController.h + SOURCES += Kickr.cpp KickrController.cpp OBJECTIVE_SOURCES += WFApi.mm }