From 347783211f97a49e2fb873c3d98277ae8d8b6d68 Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Mon, 18 Aug 2014 09:46:04 +0100 Subject: [PATCH] KICKR ANT+ Initial Support .. the code has been updated to support the Wahoo Kickr via an ANT+ USB stick. .. this requires the Kickr to be running a beta firmware of version v1.3.15. This can only be loaded via the Wahoo utility. .. for those that have access to the beta firmware this patch will work, for all others they will need to wait for the formal release by Wahoo. .. the code contains *lots* of debug and does not yet support calibration and slope mode is largely untested. --- src/ANT.cpp | 329 +++++++++++++++++++++++++++---------- src/ANT.h | 44 ++++- src/ANTChannel.cpp | 183 +++++++++++++++------ src/ANTChannel.h | 24 ++- src/ANTMessage.cpp | 120 +++++++++++++- src/ANTMessage.h | 15 ++ src/ANTlocalController.cpp | 17 +- src/ANTlocalController.h | 6 +- src/AddDeviceWizard.cpp | 1 - 9 files changed, 593 insertions(+), 146 deletions(-) diff --git a/src/ANT.cpp b/src/ANT.cpp index cd27ecc9a..e09719e75 100644 --- a/src/ANT.cpp +++ b/src/ANT.cpp @@ -25,6 +25,7 @@ #include "ANT.h" #include "ANTMessage.h" +#include "TrainSidebar.h" // for RT_MODE_{ERGO,SPIN,CALIBRATE} #include #include #include @@ -44,6 +45,9 @@ // network key const unsigned char ANT::key[8] = { 0xB9, 0xA5, 0x21, 0xFB, 0xBD, 0x72, 0xC3, 0x45 }; +// !!! THE INITIALISATION OF ant_sensor_types BELOW MUST MATCH THE +// !!! ENUM FOR CHANNELTYPE IN ANTChannel.h (SORRY, ITS HORRIBLE) + // supported sensor types const ant_sensor_type_t ANT::ant_sensor_types[] = { { true, ANTChannel::CHANNEL_TYPE_UNUSED, 0, 0, 0, 0, "Unused", '?', "" }, @@ -57,6 +61,8 @@ const ant_sensor_type_t ANT::ant_sensor_types[] = { ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Cadence", 'c', ":images/IconCadence.png" }, { true, ANTChannel::CHANNEL_TYPE_SandC, ANT_SPORT_SandC_PERIOD, ANT_SPORT_SandC_TYPE, ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Speed + Cadence", 'd', ":images/IconCadence.png" }, + { true, ANTChannel::CHANNEL_TYPE_CONTROL, ANT_SPORT_CONTROL_PERIOD, ANT_SPORT_CONTROL_TYPE, + ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Remote Control", 'r', ":images/IconCadence.png" }, { false, ANTChannel::CHANNEL_TYPE_KICKR, ANT_SPORT_KICKR_PERIOD, ANT_SPORT_POWER_TYPE, ANT_KICKR_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Kickr", 'k', ":images/IconCadence.png" }, { false, ANTChannel::CHANNEL_TYPE_GUARD, 0, 0, 0, 0, "", '\0', "" } @@ -86,6 +92,22 @@ ANT::ANT(QObject *parent, DeviceConfiguration *devConf) : QThread(parent), devCo powerchannels=0; configuring = false; + // kickr command channel + kickrTimer = NULL; + kickrCounter = 0; + kickrSequence = 0; + kickrCommandChannel = -1; + kickrDeviceID = 0; + + // current and desired modes/load/gradients + // set so first time through current != desired + currentMode = 0; + mode = RT_MODE_ERGO; + currentLoad = 0; + load = 100; // always set to something + currentGradient = 0; + gradient = 0.1; + // state machine state = ST_WAIT_FOR_SYNC; length = bytes = 0; @@ -222,6 +244,150 @@ void ANT::run() } } +void +ANT::setLoad(double load) +{ + if (this->load == load) return; + + // load has changed + this->load = load; +} +void +ANT::setGradient(double gradient) +{ + if (this->gradient == gradient) return; + + // gradient changed + this->gradient = gradient; +} +void +ANT::setMode(int mode) +{ + if (this->mode == mode) return; + + // mode changed + this->mode = mode; +} + +void +ANT::kickrCommand() +{ + static int lastSequence = -1; + + // mode changed ? + if (currentMode != mode) { + + switch(mode) { + case RT_MODE_ERGO : // do nothing, just start sending ergo commands below + //qDebug()<<"A: setup ergo mode"; + break; + + case RT_MODE_SPIN : // need to setup for "sim" mode, so sending lots of + // config to overcome the default values + { + + // Slope mode is called "Simulation" mode on a Kickr + // We need to set paramters up before we set it and then we + // can simply adjust the gradient from there on + //qDebug()<<"A: setup slope mode"; + + // set rolling resistance (Crr) + sendMessage(ANTMessage::kickrRollingResistance(kickrCommandChannel, ++kickrSequence, kickrDeviceID, 0.004f)); + + // set wind resistance (C) + // where C = A*Cw*Rho + // A is effective frontal area (m^2) + // Cw is the drag coefficent (unitless) + // Rho is the air density (kg/m^3). + // + // So we default to; A of 0.4 (on the hoods), CdA 1.0 (on the hoods), Rho 1.225 (at sea level) + // for more background to this see: http://www.cyclingpowerlab.com/cyclingaerodynamics.aspx + // + // Our default is therefore 0.4 * 1.0 * 1.225 = 0.49 + sendMessage(ANTMessage::kickrWindResistance(kickrCommandChannel, ++kickrSequence, kickrDeviceID, 0.49)); + + // athlete weight default to 75kg - we don't have a context to refer to ... + //double weight = appsettings->cvalue(context->athlete->cyclist, GC_WEIGHT, "75.0").toString().toDouble(); + double weight = 75.0; // in kg + + // plus bike, lets be generous and say no water bottles ;) + weight += 10.0; + + // set sim mode - we need the athlete weight, use from athlete settings for now + sendMessage(ANTMessage::kickrSimMode(kickrCommandChannel, ++kickrSequence, kickrDeviceID, weight)); + + } + break; + + case RT_MODE_CALIBRATE : // ??? maybe ??? + //qDebug()<<"A: setup calib mode"; + break; + } + + currentMode = mode; + currentLoad = load; + currentGradient = gradient; + kickrSequence++; + kickrCounter = 0; + } + + // load has changed ? + if (mode == RT_MODE_ERGO && load != currentLoad) { + currentLoad = load; + kickrCounter = 0; + kickrSequence++; + } + + // slope has changed in slope mode + if (mode == RT_MODE_SPIN && gradient != currentGradient) { + currentGradient = gradient; + kickrCounter = 0; + kickrSequence++; + } + + // 10s timeout ? + // 10000ms / 60ms = 166.666666 + if (kickrCounter++ > 165) { + kickrCounter = 0; + kickrSequence++; + } + + // create message to send + ANTMessage toSend; + + switch (mode) { + + default: + case RT_MODE_ERGO: + toSend = ANTMessage::kickrErgMode(kickrCommandChannel, kickrSequence, kickrDeviceID, load, false); + break; + + case RT_MODE_SPIN: // gradient is between -1.0 and +1.0 so needs conversion from gradient + toSend = ANTMessage::kickrGrade(kickrCommandChannel, kickrSequence, kickrDeviceID, gradient/100.00f); + break; + } + + // log out put + if (lastSequence != kickrSequence) { + //qDebug()< 4) addDevice(0, ANTChannel::CHANNEL_TYPE_SandC, 4); + if (channels > 4) { + addDevice(0, ANTChannel::CHANNEL_TYPE_SandC, 4); + } } } @@ -320,10 +488,14 @@ ANT::pause() int ANT::stop() { + // stop kickr commands + if (kickrTimer && kickrTimer->isActive()) { + kickrTimer->stop(); + } + // Close the connections to ANT devices before we stop. Sending the // "close channel" ANT message seems to resolve an intermittent // issue of unresponsive USB2 stick on subsequent opens. - if (antIDs.count()) { foreach(QString antid, antIDs) { if (antid.length()) { @@ -336,6 +508,11 @@ ANT::stop() } } + // and close any channels that are open + for (int i=0; istatus != ANTChannel::Closed) + antChannel[i]->close(); + // what state are we in anyway? pvars.lock(); Status = 0; // Terminate it! @@ -359,21 +536,17 @@ ANT::quit(int code) void ANT::getRealtimeData(RealtimeData &rtData) { - int mode = rtData.mode; - long load = rtData.getLoad(); - double slope = rtData.getSlope(); - rtData = telemetry; rtData.mode = mode; rtData.setLoad(load); - rtData.setSlope(slope); + rtData.setSlope(gradient); } /*====================================================================== * Channel management *====================================================================*/ -// returns 1 for success, 0 for fail. +// returns -1 for fail otherwise channel number used int ANT::addDevice(int device_number, int device_type, int channel_number) { @@ -381,7 +554,7 @@ ANT::addDevice(int device_number, int device_type, int channel_number) if (channel_number>-1) { //antChannel[channel_number]->close(); antChannel[channel_number]->open(device_number, device_type); - return 1; + return channel_number; } // if we already have the device, then return. @@ -391,10 +564,10 @@ ANT::addDevice(int device_number, int device_type, int channel_number) // and 0p on channel 1 if (device_number != 0) { for (int i=0; ichannel_type & 0xf ) == device_type) && + if ((antChannel[i]->channel_type == device_type) && (antChannel[i]->device_number == device_number)) { // send the channel found... - return 1; + return i; } } } @@ -416,12 +589,12 @@ ANT::addDevice(int device_number, int device_type, int channel_number) // increment the number of power channels powerchannels++; } - return 1; + return i; } } // there are no unused channels. fail. - return 0; + return -1; } // returns 1 for successfully removed, 0 for none found. @@ -434,10 +607,6 @@ ANT::removeDevice(int device_number, int channel_type) ANTChannel *ac = antChannel[i]; if ((ac->channel_type == channel_type) && (ac->device_number == device_number)) { - - if ((ac->control_channel!=ac) && ac->control_channel) - removeDevice(device_number, ac->control_channel->channel_type); - ac->close(); ac->channel_type=ANTChannel::CHANNEL_TYPE_UNUSED; ac->device_number=0; @@ -491,60 +660,6 @@ ANT::startWaitingSearch() return 0; } -void -ANT::associateControlChannels() { - - // first, unassociate all control channels - for (int i=0; icontrol_channel=NULL; - - // then, associate cinqos: - // new cinqos get their own selves for control - // old cinqos, look for an open control channel - // if found and open, associate - // elif found and not open yet, nop - // elif not found, open one - for (int i=0; ichannel_type) { - case ANTChannel::CHANNEL_TYPE_POWER: - if (ac->is_cinqo) { - if (ac->is_old_cinqo) { - ANTChannel *my_ant_channel; - - my_ant_channel=findDevice(ac->device_number, ANTChannel::CHANNEL_TYPE_QUARQ); - if (!my_ant_channel) my_ant_channel=findDevice(ac->device_number, ANTChannel::CHANNEL_TYPE_FAST_QUARQ); - if (!my_ant_channel) my_ant_channel=findDevice(ac->device_number, ANTChannel::CHANNEL_TYPE_FAST_QUARQ_NEW); - - if (my_ant_channel) { - if (my_ant_channel->isSearching()) { - // ignore if searching - } else { - ac->control_channel=my_ant_channel; - ac->sendCinqoSuccess(); - } - } else { // no ant channel, let's start one - addDevice(ac->device_number, ANTChannel::CHANNEL_TYPE_QUARQ, -1); - } - } else { // new cinqo - ac->control_channel=ac; - ac->sendCinqoSuccess(); - } - } // is_cinqo - break; - - case ANTChannel::CHANNEL_TYPE_FAST_QUARQ: - case ANTChannel::CHANNEL_TYPE_FAST_QUARQ_NEW: - case ANTChannel::CHANNEL_TYPE_QUARQ: - ac->is_cinqo=1; - ac->control_channel=ac; - break; - default: - ; - } // channel_type case - } // for-loop -} - // For serial device discovery bool ANT::discover(QString name) @@ -579,7 +694,51 @@ Q_UNUSED(name); void ANT::channelInfo(int channel, int device_number, int device_id) { + qDebug()<<"** CONNECTION ESTABLISHED channel:"<is_kickr && antChannel[channel]->command_channel == NULL) { + + kickrDeviceID = device_number; + kickrCommandChannel = addDevice(device_number, ANTChannel::CHANNEL_TYPE_KICKR, -1); + + if (kickrCommandChannel >= 0) { + + //qDebug()<<"opened kickr "<< kickrDeviceID<< "communication channel is:"<command_channel = antChannel[kickrCommandChannel]; + + // start timer to send commands every 63ms + if (kickrTimer) { + + // if one already active lets stop it + if (kickrTimer->isActive()) kickrTimer->stop(); + + } else { + + // first time lets get a new timer + kickrTimer = new QTimer(this); + kickrTimer->setInterval(KICKR_COMMAND_INTERVAL); + + // connect up the dots + connect(kickrTimer, SIGNAL(timeout()), this, SLOT(kickrCommand())); + } + + // lets go + kickrCounter = 0; + kickrSequence = 0; + kickrTimer->start(); + + } else { + + // need to find a way to communicate back on error + qDebug()<<"kickr setup failed, no channels available"; + } + } + //qDebug()<<"found device number"<= channels) return; // ignore out of bound emit lostDevice(number); - //qDebug()<<"lost info for channel"<= channels) return; // ignore out of bound - //qDebug()<<"stale info for channel"<= channels) return; // ignore out of bound emit searchTimeout(number); - //qDebug()<<"search timeout on channel"<= channels) return; // ignore out of bound emit searchComplete(number); - //qDebug()<<"search completed on channel"< #include #include +#include #include #include @@ -166,6 +167,8 @@ struct setChannelAtom { #define ANT_CW_INIT 0x53 #define ANT_CW_TEST 0x48 +#define TRANSITION_START 0x00 // start of transition when opening + // ANT message structure. #define ANT_OFFSET_SYNC 0 #define ANT_OFFSET_LENGTH 1 @@ -209,6 +212,7 @@ struct setChannelAtom { #define ANT_SPORT_SPEED_PERIOD 8118 #define ANT_SPORT_CADENCE_PERIOD 8102 #define ANT_SPORT_SandC_PERIOD 8086 +#define ANT_SPORT_CONTROL_PERIOD 8192 #define ANT_SPORT_KICKR_PERIOD 2048 #define ANT_FAST_QUARQ_PERIOD (8182/16) #define ANT_QUARQ_PERIOD (8182*4) @@ -218,6 +222,7 @@ struct setChannelAtom { #define ANT_SPORT_SPEED_TYPE 0x7B #define ANT_SPORT_CADENCE_TYPE 0x7A #define ANT_SPORT_SandC_TYPE 0x79 +#define ANT_SPORT_CONTROL_TYPE 0x10 #define ANT_FAST_QUARQ_TYPE_WAS 11 // before release 1.8 #define ANT_FAST_QUARQ_TYPE 0x60 #define ANT_QUARQ_TYPE 0x60 @@ -242,6 +247,26 @@ struct setChannelAtom { #define ANT_SPORT_AUTOZERO_OFF 0x00 #define ANT_SPORT_AUTOZERO_ON 0x01 +// kickr +#define KICKR_COMMAND_INTERVAL 60 // every 60 ms +#define KICKR_SET_RESISTANCE_MODE 0x40 +#define KICKR_SET_STANDARD_MODE 0x41 +#define KICKR_SET_ERG_MODE 0x42 +#define KICKR_SET_SIM_MODE 0x43 +#define KICKR_SET_CRR 0x44 +#define KICKR_SET_C 0x45 +#define KICKR_SET_GRADE 0x46 +#define KICKR_SET_WIND_SPEED 0x47 +#define KICKR_SET_WHEEL_CIRCUMFERENCE 0x48 +#define KICKR_INIT_SPINDOWN 0x49 +#define KICKR_READ_MODE 0x4A +#define KICKR_SET_FTP_MODE 0x4B +// 0x4C-0x4E reserved. +#define KICKR_CONNECT_ANT_SENSOR 0x4F +// 0x51-0x59 reserved. +#define KICKR_SPINDOWN_RESULT 0x5A + + //====================================================================== // Worker thread //====================================================================== @@ -299,6 +324,12 @@ public slots: // get telemetry void getRealtimeData(RealtimeData &); // return current realtime data + // kickr command loading - only ANT device we know about to do this so not generic + void setLoad(double); + void setGradient(double); + void setMode(int); + void kickrCommand(); + public: static int interpretSuffix(char c); // utility to convert e.g. 'c' to CHANNEL_TYPE_CADENCE @@ -322,7 +353,6 @@ public: int removeDevice(int device_number, int channel_type); ANTChannel *findDevice(int device_number, int channel_type); int startWaitingSearch(); - void associateControlChannels(); // transmission void sendMessage(ANTMessage); @@ -402,6 +432,18 @@ private: QQueue channelQueue; // messages for configuring channels from controller + // generic trainer settings + double currentLoad, load; + double currentGradient, gradient; + int currentMode, mode; + + // now kickr specific + QTimer *kickrTimer; + int kickrCommandChannel; + int kickrCounter; // 1-16 for each period + unsigned char kickrSequence; + int kickrDeviceID; + }; #include "ANTMessage.h" diff --git a/src/ANTChannel.cpp b/src/ANTChannel.cpp index 8badd4d28..4424c3da2 100644 --- a/src/ANTChannel.cpp +++ b/src/ANTChannel.cpp @@ -35,16 +35,16 @@ ANTChannel::init() { channel_type=CHANNEL_TYPE_UNUSED; channel_type_flags=0; + is_kickr=false; is_cinqo=0; is_old_cinqo=0; is_alt=0; - control_channel=NULL; + command_channel=NULL; // kickr manufacturer_id=0; product_id=0; product_version=0; device_number=0; device_id=0; - channel_assigned=0; state=ANT_UNASSIGN_CHANNEL; blanked=1; messages_received=0; @@ -53,6 +53,7 @@ ANTChannel::init() srm_offset=400; // default relatively arbitrary, but matches common 'indoors' values burstInit(); value2=value=0; + status = Closed; } // @@ -95,7 +96,12 @@ void ANTChannel::open(int device, int chan_type) device_number=device; setId(); - attemptTransition(ANT_UNASSIGN_CHANNEL); + // lets open the channel + qDebug()<<"** OPENING CHANNEL"<sendMessage(ANTMessage::close(number)); init(); } @@ -118,17 +129,21 @@ void ANTChannel::receiveMessage(unsigned char *ant_message) break; case ANT_BROADCAST_DATA: broadcastEvent(ant_message); + qDebug()<<"broadcast event:"<sendMessage(ANTMessage::unassignChannel(number)); + qDebug()<sendMessage(ANTMessage::unassignChannel(number)); + } } else if (MESSAGE_IS_EVENT_RX_SEARCH_TIMEOUT(message)) { + qDebug()<> exit(-10); } else if (MESSAGE_IS_EVENT_TRANSFER_TX_COMPLETED(message)) { + // do nothing - } + + } else { + + // usually a response event, so lets get a debug going + qDebug()<sendMessage(ANTMessage::requestMessage(number, ANT_CHANNEL_ID)); blanking_timestamp=get_timestamp(); @@ -291,9 +324,6 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) // Power case CHANNEL_TYPE_POWER: - case CHANNEL_TYPE_QUARQ: - case CHANNEL_TYPE_FAST_QUARQ: - case CHANNEL_TYPE_FAST_QUARQ_NEW: // what kind of power device is this? switch(antMessage.data_page) { @@ -482,13 +512,16 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) // HR case CHANNEL_TYPE_HR: { +qDebug()<<"hr broadcast!"; // cadence first... uint16_t time = antMessage.measurementTime - lastMessage.measurementTime; +qDebug()<<"time="<setBPM(antMessage.instantHeartrate); value2 = value = antMessage.instantHeartrate; +qDebug()<<"instant="<sendMessage(ANTMessage::setSearchTimeout(number, (int)(timeout_lost/2.5))); } channel_type_flags &= ~CHANNEL_TYPE_QUICK_SEARCH; @@ -661,63 +702,103 @@ void ANTChannel::burstData(unsigned char *ant_message) { } } -// choose this one.. -void ANTChannel::setChannelID(int device_number, int device_id, int /*txtype*/) -{ - parent->sendMessage(ANTMessage::setChannelID(number, device_number, device_id, 0)); // lets go back to allowing anything - parent->sendMessage(ANTMessage::open(number)); // lets go back to allowing anything -} - +// TRANSITIONING FROM NOT OPEN TO OPEN AND BACK +// this is basically a transition from an unassigned channel +// to one with a device open on it and works from top to bottom +// it is first called by open() to unassign channel and then +// after each message response from the device we get called here +// to perform the next action in the sequence void ANTChannel::attemptTransition(int message_id) { + // ignore all the nonsense if we are not trying to transition ! + if (status != Closing && status != Opening) { + qDebug()<ant_sensor_types[channel_type]); + qDebug()<sendMessage(ANTMessage::unassignChannel(number)); // unassign whatever we had before + // drops through into assign channel because if the channel currently has no + // assignment the unassign channel message will generate an error response not + // an unassign channel response. But we don't really know what state the device + // might be in, so we unassign then assign at the beginning of a transition + + case ANT_UNASSIGN_CHANNEL: + qDebug()<sendMessage(ANTMessage::assignChannel(number, 0, st->network)); // recieve channel on network 1 + if (channel_type == CHANNEL_TYPE_KICKR) { + qDebug()<<"assign channel tx for kickr"; + // channel is a transmit not receiving channel + parent->sendMessage(ANTMessage::assignChannel(number, CHANNEL_TYPE_TX, st->network)); // recieve channel on network 1 + + } else { + + qDebug()<<"assign channel rx for NOT kickr"<type; + // assign and set channel id all in one + parent->sendMessage(ANTMessage::assignChannel(number, CHANNEL_TYPE_RX, st->network)); // recieve channel on network 1 + } device_id=st->device_id; - parent->sendMessage(ANTMessage::setChannelID(number, 0, device_id, 0)); // lets go back to allowing anything setId(); break; case ANT_ASSIGN_CHANNEL: - channel_assigned=1; - parent->sendMessage(ANTMessage::setChannelID(number, device_number, device_id, 0)); + + qDebug()<sendMessage(ANTMessage::setChannelID(number, device_number, device_id, 0)); // we need to be specific! + } else { + + qDebug()<<"assign channel id for NOT kickr"<type; + parent->sendMessage(ANTMessage::setChannelID(number, 0, device_id, 0)); // lets go back to allowing anything + } break; case ANT_CHANNEL_ID: - if (channel_type & CHANNEL_TYPE_QUICK_SEARCH) { - parent->sendMessage(ANTMessage::setSearchTimeout(number, (int)(timeout_scan/2.5))); + qDebug()<sendMessage(ANTMessage::setSearchTimeout(number, 255)); + } else { - parent->sendMessage(ANTMessage::setSearchTimeout(number, (int)(timeout_lost/2.5))); + qDebug()<sendMessage(ANTMessage::setSearchTimeout(number, (int)(timeout_scan/2.5))); + } else { + parent->sendMessage(ANTMessage::setSearchTimeout(number, (int)(timeout_lost/2.5))); + } } break; case ANT_SEARCH_TIMEOUT: + qDebug()<period; parent->sendMessage(ANTMessage::setChannelPeriod(number, st->period)); } else { // we are setting the ant_search timeout after connected @@ -727,16 +808,32 @@ void ANTChannel::attemptTransition(int message_id) break; case ANT_CHANNEL_PERIOD: + qDebug()<frequency; parent->sendMessage(ANTMessage::setChannelFreq(number, st->frequency)); break; case ANT_CHANNEL_FREQUENCY: + qDebug()<sendMessage(ANTMessage::open(number)); mi.initialise(); break; case ANT_OPEN_CHANNEL: - parent->sendMessage(ANTMessage::open(number)); + qDebug()<sendMessage(ANTMessage::open(number)); + break; + + case ANT_CLOSE_CHANNEL: + // next step is unassign and start over + // but we must wait until event_channel_closed + // which is its own channel event + state=MESSAGE_RECEIVED; + qDebug()<sendMessage(ANTMessage::setSearchTimeout(number, seconds/2.5)); -} - // Calibrate... needs fixing in version 3.1 // request the device on this channel calibrates itselt void ANTChannel::requestCalibrate() { diff --git a/src/ANTChannel.h b/src/ANTChannel.h index 7cec8fbe9..e12859ffa 100644 --- a/src/ANTChannel.h +++ b/src/ANTChannel.h @@ -28,6 +28,8 @@ /* after fast search, wait for slow search. Otherwise, starting slow search might postpone the fast search on another channel. */ #define CHANNEL_TYPE_WAITING 0x20 +#define CHANNEL_TYPE_RX 0x0 +#define CHANNEL_TYPE_TX 0x10 #define CHANNEL_TYPE_PAIR 0x40 // to do an Ant pair #define MESSAGE_RECEIVED -1 @@ -86,7 +88,6 @@ class ANTChannel : public QObject { double blanking_timestamp; int blanked; char id[10]; // short identifier - bool channel_assigned; ANTChannelInitialisation mi; int messages_received; // for signal strength metric @@ -105,6 +106,8 @@ class ANTChannel : public QObject { public: + // !!! THIS ENUM LIST MUST MATCH THE ORDER THAT ant_sensor_type_t + // !!! IS INITIALISED IN ANT.cpp (SORRY, ITS HORRIBLE) enum channeltype { CHANNEL_TYPE_UNUSED, CHANNEL_TYPE_HR, @@ -112,14 +115,20 @@ class ANTChannel : public QObject { CHANNEL_TYPE_SPEED, CHANNEL_TYPE_CADENCE, CHANNEL_TYPE_SandC, - CHANNEL_TYPE_QUARQ, - CHANNEL_TYPE_FAST_QUARQ, - CHANNEL_TYPE_FAST_QUARQ_NEW, + CHANNEL_TYPE_CONTROL, CHANNEL_TYPE_KICKR, CHANNEL_TYPE_GUARD }; typedef enum channeltype ChannelType; + // lets track the status as we open and close a channel + enum channelstatus { + Closed, + Opening, + Open, + Closing + } status; + // Channel Information - to save tedious set/getters made public int number; // Channel number within Ant chip int state; @@ -136,7 +145,10 @@ class ANTChannel : public QObject { int search_type; int srm_offset; - ANTChannel *control_channel; + + // this is a command channel used by the Kickr and possibly + // will be of use for other devices. It is probably rather + ANTChannel *command_channel; ANTChannel(int number, ANT *parent); @@ -157,11 +169,9 @@ class ANTChannel : public QObject { void broadcastEvent(unsigned char *message); void ackEvent(unsigned char *message); void channelId(unsigned char *message); - void setChannelID(int device, int id, int txtype); void setId(); void requestCalibrate(); void attemptTransition(int message_code); - void setTimeout(int seconds); // telemetry for this channel double channelValue() { return value; } diff --git a/src/ANTMessage.cpp b/src/ANTMessage.cpp index 25759e41e..c6b9fc7be 100644 --- a/src/ANTMessage.cpp +++ b/src/ANTMessage.cpp @@ -369,6 +369,7 @@ ANTMessage::ANTMessage(ANT *parent, const unsigned char *message) { case ANTChannel::CHANNEL_TYPE_HR: channel = message[3]; measurementTime = message[8] + (message[9]<<8); +qDebug()<<"measurementtime="<>8), + (unsigned char)usWatts, (unsigned char)(usWatts>>8), + (unsigned char)(bSimSpeed) ? 1 : 0); +} + +ANTMessage ANTMessage::kickrSlopeMode(const unsigned char channel, unsigned char seq, ushort usDeviceId, ushort scale) +{ + return ANTMessage(7, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_SET_RESISTANCE_MODE, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8), // preamble + (unsigned char)scale, (unsigned char)(scale>>8)); +} + +ANTMessage ANTMessage::kickrSimMode(const unsigned char channel, unsigned char seq, ushort usDeviceId, float fWeight) +{ + // weight encoding + ushort usWeight = (ushort)(fWeight * 100.0); + + return ANTMessage(7, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_SET_SIM_MODE, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8), // preamble + (unsigned char)usWeight, (unsigned char)(usWeight>>8)); +} + +ANTMessage ANTMessage::kickrStdMode(const unsigned char channel, unsigned char seq, ushort usDeviceId, ushort eLevel) +{ + return ANTMessage(7, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_SET_STANDARD_MODE, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8), // preamble + (unsigned char)eLevel, (unsigned char)(eLevel>>8)); +} + +ANTMessage ANTMessage::kickrFtpMode(const unsigned char channel, unsigned char seq, ushort usDeviceId, ushort usFtp, ushort usPercent) +{ + return ANTMessage(9, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_SET_FTP_MODE, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8), // preamble + (unsigned char)usFtp, (unsigned char)(usFtp>>8), + (unsigned char)usPercent, (unsigned char)(usPercent>>8)); +} + +ANTMessage ANTMessage::kickrRollingResistance(const unsigned char channel, unsigned char seq, ushort usDeviceId, float fCrr) +{ + // crr encoding + ushort usCrr = (ushort)(fCrr * 10000.0); + + return ANTMessage(7, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_SET_CRR, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8), // preamble + (unsigned char)usCrr, (unsigned char)(usCrr>>8)); +} + +ANTMessage ANTMessage::kickrWindResistance(const unsigned char channel, unsigned char seq, ushort usDeviceId, float fC) +{ + // wind resistance encoding + ushort usC = (ushort)(fC * 1000.0); + + return ANTMessage(7, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_SET_C, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8), // preamble + (unsigned char)usC, (unsigned char)(usC>>8)); +} + +ANTMessage ANTMessage::kickrGrade(const unsigned char channel, unsigned char seq, ushort usDeviceId, float fGrade) +{ + + // grade encoding + if (fGrade > 1) fGrade = 1; + if (fGrade < -1) fGrade = -1; + + fGrade = fGrade + 1; + ushort usGrade = (ushort)(fGrade * 65536 / 2); + + return ANTMessage(7, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_SET_GRADE, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8), // preamble + (unsigned char)usGrade, (unsigned char)(usGrade>>8)); +} + +ANTMessage ANTMessage::kickrWindSpeed(const unsigned char channel, unsigned char seq, ushort usDeviceId, float mpsWindSpeed) +{ + // windspeed encoding + if (mpsWindSpeed > 32.768f) mpsWindSpeed = 32.768f; + if (mpsWindSpeed < -32.768f) mpsWindSpeed = -32.768f; + + // the wind speed is transmitted in 1/1000 mps resolution. + mpsWindSpeed = mpsWindSpeed + 32.768f; + ushort usSpeed = (ushort)(mpsWindSpeed * 1000); + + return ANTMessage(7, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_SET_WIND_SPEED, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8), // preamble + (unsigned char)usSpeed, (unsigned char)(usSpeed>>8)); +} + +ANTMessage ANTMessage::kickrWheelCircumference(const unsigned char channel, unsigned char seq, ushort usDeviceId, float mmCircumference) +{ + // circumference encoding + ushort usCirc = (ushort)(mmCircumference * 10.0); + + return ANTMessage(7, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_SET_WHEEL_CIRCUMFERENCE, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8), // preamble + (unsigned char)usCirc, (unsigned char)(usCirc>>8)); +} + +ANTMessage ANTMessage::kickrReadMode(const unsigned char channel, unsigned char seq, ushort usDeviceId) +{ + return ANTMessage(5, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_INIT_SPINDOWN, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8)); // preamble +} + +ANTMessage ANTMessage::kickrInitSpindown(const unsigned char channel, unsigned char seq, ushort usDeviceId) +{ + return ANTMessage(5, ANT_BROADCAST_DATA, channel, // broadcast + KICKR_READ_MODE, seq, (unsigned char)usDeviceId, (unsigned char)(usDeviceId>>8)); // preamble +} diff --git a/src/ANTMessage.h b/src/ANTMessage.h index a3659c2ae..2f4056f26 100644 --- a/src/ANTMessage.h +++ b/src/ANTMessage.h @@ -68,6 +68,21 @@ class ANTMessage { static ANTMessage open(const unsigned char channel); static ANTMessage close(const unsigned char channel); + // kickr command channel messages all sent as broadcast data + // over the command channel as type 0x4E + static ANTMessage kickrErgMode(const unsigned char channel, unsigned char seq, ushort usDeviceId, ushort usWatts, bool bSimSpeed); + static ANTMessage kickrFtpMode(const unsigned char channel, unsigned char seq, ushort usDeviceId, ushort usFtp, ushort usPercent); + static ANTMessage kickrSlopeMode(const unsigned char channel, unsigned char seq, ushort usDeviceId, ushort scale); + static ANTMessage kickrStdMode(const unsigned char channel, unsigned char seq, ushort usDeviceId, ushort eLevel); + static ANTMessage kickrSimMode(const unsigned char channel, unsigned char seq, ushort usDeviceId, float fWeight); + static ANTMessage kickrWindResistance(const unsigned char channel, unsigned char seq, ushort usDeviceId, float fC) ; + static ANTMessage kickrRollingResistance(const unsigned char channel, unsigned char seq, ushort usDeviceId, float fCrr); + static ANTMessage kickrGrade(const unsigned char channel, unsigned char seq, ushort usDeviceId, float fGrade); + static ANTMessage kickrWindSpeed(const unsigned char channel, unsigned char seq, ushort usDeviceId, float mpsWindSpeed); + static ANTMessage kickrWheelCircumference(const unsigned char channel, unsigned char seq, ushort usDeviceId, float mmCircumference); + static ANTMessage kickrReadMode(const unsigned char channel, unsigned char seq, ushort usDeviceId); + static ANTMessage kickrInitSpindown(const unsigned char channel, unsigned char seq, ushort usDeviceId); + // convert a channel event message id to human readable string static const char * channelEventMessage(unsigned char c); diff --git a/src/ANTlocalController.cpp b/src/ANTlocalController.cpp index b15706564..b09398821 100644 --- a/src/ANTlocalController.cpp +++ b/src/ANTlocalController.cpp @@ -90,7 +90,22 @@ ANTlocalController::discover(QString name) bool ANTlocalController::doesPush() { return false; } bool ANTlocalController::doesPull() { return true; } -bool ANTlocalController::doesLoad() { return false; } +bool ANTlocalController::doesLoad() { return true; } + +void +ANTlocalController::setLoad(double x) { + myANTlocal->setLoad(x); +} + +void +ANTlocalController::setGradient(double x) { + myANTlocal->setGradient(x); +} + +void +ANTlocalController::setMode(int mode) { + myANTlocal->setMode(mode); +} /* * gets called from the GUI to get updated telemetry. diff --git a/src/ANTlocalController.h b/src/ANTlocalController.h index 4f355381b..621ecdb09 100644 --- a/src/ANTlocalController.h +++ b/src/ANTlocalController.h @@ -59,7 +59,11 @@ public: bool doesPush(), doesPull(), doesLoad(); void getRealtimeData(RealtimeData &rtData); void pushRealtimeData(RealtimeData &rtData); - void setLoad(double) { return; } + + // now with the kickr we can control trainers + void setLoad(double); + void setGradient(double); + void setMode(int); signals: void foundDevice(int channel, int device_number, int device_id); // channelInfo diff --git a/src/AddDeviceWizard.cpp b/src/AddDeviceWizard.cpp index 1cac4ef8d..8eb7c3298 100644 --- a/src/AddDeviceWizard.cpp +++ b/src/AddDeviceWizard.cpp @@ -139,7 +139,6 @@ AddType::clicked(QString p) 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 }