Files
GoldenCheetah/src/BT40.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

277 lines
6.6 KiB
C++

/*
* 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 "BT40.h"
BT40::BT40(QObject *parent, DeviceConfiguration *devConf) : QThread(parent)
{
this->parent = parent;
this->devConf = devConf;
scanned = false;
mode = -1;
load = 100;
slope = 1.0;
connect(WFApi::getInstance(), SIGNAL(discoveredDevices(int,bool)), this, SLOT(discoveredDevices(int,bool)));
}
BT40::~BT40()
{
stop();
}
// not required
void BT40::setDevice(QString) { }
void BT40::setMode(int mode, double load, double gradient)
{
pvars.lock();
this->mode = mode;
this->load = load;
this->slope = gradient;
pvars.unlock();
}
void BT40::setLoad(double load)
{
pvars.lock();
if (load > 1500) load = 1500;
if (load < 50) load = 50;
this->load = load;
pvars.unlock();
}
void BT40::setGradient(double gradient)
{
pvars.lock();
this->slope = gradient;
pvars.unlock();
}
int BT40::getMode()
{
int tmp;
pvars.lock();
tmp = mode;
pvars.unlock();
return tmp;
}
double BT40::getLoad()
{
double tmp;
pvars.lock();
tmp = load;
pvars.unlock();
return tmp;
}
double BT40::getGradient()
{
double tmp;
pvars.lock();
tmp = slope;
pvars.unlock();
return tmp;
}
void
BT40::getRealtimeData(RealtimeData &rtData)
{
pvars.lock();
rtData = rt;
pvars.unlock();
}
int
BT40::start()
{
QThread::start();
return 0;
}
// does nothing - neither does pause
int BT40::restart() { return 0; }
int BT40::pause() { return 0; }
int BT40::stop()
{
running = false;
return 0;
}
// used by thread to set variables and emit event if needed
// on unexpected exit
int BT40::quit(int code)
{
// event code goes here!
exit(code);
return 0; // never gets here obviously but shuts up the compiler!
}
/*----------------------------------------------------------------------
* MAIN THREAD - READS TELEMETRY AND UPDATES LOAD/GRADIENT ON KICKR
*----------------------------------------------------------------------*/
void BT40::run()
{
int currentmode = -1;
int currentload = -1;
double currentslope= -1;
// Connect to the device
if (connectBT40()) {
quit(2);
return; // open failed!
}
running = true;
while(running) {
// only get busy if we're actually connected
if (WFApi::getInstance()->isConnected(sd)) {
// We ALWAYS set load for each loop. This is because
// even though the device reports as connected we need
// to wait before it really is. So we just keep on
// sending the current mode/load. It doesn't cost us
// anything since all devices are powered.
// it does generate a few error messages though..
// and the connection takes about 25 secs to get
// up to speed.
// set load - reset it if generated watts don't match ..
if (mode == RT_MODE_ERGO) {
WFApi::getInstance()->setErgoMode(sd);
WFApi::getInstance()->setLoad(sd, load);
currentload = load;
currentmode = mode;
}
// set slope
if (mode == RT_MODE_SLOPE && currentslope) {
WFApi::getInstance()->setSlopeMode(sd);
WFApi::getInstance()->setSlope(sd, slope);
currentslope = slope;
currentmode = mode;
}
}
if (WFApi::getInstance()->hasData(sd)) {
pvars.lock();
WFApi::getInstance()->getRealtimeData(sd, &rt);
// set speed from wheelRpm and configured wheelsize
double x = rt.getWheelRpm();
if (devConf) rt.setSpeed(x * (devConf->wheelSize/1000) * 60 / 1000);
else rt.setSpeed(x * 2.10 * 60 / 1000);
pvars.unlock();
}
// lets not hog cpu
msleep(100);
}
disconnectBT40();
quit(0);
}
void
BT40::discoveredDevices(int n, bool finished)
{
WFApi *w = WFApi::getInstance();
// need to emit signal with device uuid and type
// this is used by the add device wizard.
// but only emit as they are found, not at the end
// when search times out -- we want them as they
// arrive.
if (!finished && w->deviceSubType(n-1) != WFApi::WF_SENSOR_SUBTYPE_BIKE_POWER_KICKR) {
for(int i=0; i<n; i++) {
qDebug()<<this<<"BT40 discovered a bluetooth device.."<<i
<<w->deviceUUID(i)
<<w->deviceType(i);
emit foundDevice(w->deviceUUID(i), w->deviceType(i));
}
}
}
bool
BT40::find()
{
WFApi *w = WFApi::getInstance();
// do we even have BTLE hardware available?
if (w->isBTLEEnabled() ==false) {
// lets try and enable it
// won't complete for a while but hopefully before next attempt to find is made
// by user or device scanner in AddDeviceWizard
w->enableBTLE(true, false);
return false;
}
// can we kick off a search?
if (w->discoverDevicesOfType(WFApi::WF_SENSORTYPE_NONE) == false) return false;
// wait for it to return something, anything
QEventLoop loop;
connect(w, SIGNAL(discoverFinished()), &loop, SLOT(quit()));
loop.exec();
scanned = true;
// what did we get?
if (w->deviceCount()) {
deviceUUID = w->deviceUUID(0);
return true;
} else return false;
}
int
BT40::connectBT40()
{
// get a pool for this thread
pool = WFApi::getInstance()->getPool();
sd = WFApi::getInstance()->connectDevice(devConf->portSpec);
return 0;
}
int
BT40::disconnectBT40()
{
// disconnect
WFApi::getInstance()->disconnectDevice(sd);
connected = false;
// clear that pool now we're done
WFApi::getInstance()->freePool(pool);
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 BT40::discover(QString)
{
return false;
}