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 }