/* * Copyright (c) 2009 Mark Rages * 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 */ //------------------------------------------------------------------------ // This code has been created by folding the ANT.cpp source with // the Quarqd source provided by Mark Rages and the Serial device // code from Computrainer.cpp //------------------------------------------------------------------------ #include "ANT.h" #include "ANTMessage.h" #include "TrainSidebar.h" // for RT_MODE_{ERGO,SPIN,CALIBRATE} #include #include #include #include #include "RealtimeData.h" #ifdef Q_OS_LINUX // to get stat /dev/xxx for major/minor #include #include #include #endif /* Control status */ #define ANT_RUNNING 0x01 #define ANT_PAUSED 0x02 // 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", '?', "" }, { true, ANTChannel::CHANNEL_TYPE_HR, ANT_SPORT_HR_PERIOD, ANT_SPORT_HR_TYPE, ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Heartrate", 'h', ":images/IconHR.png" }, { true, ANTChannel::CHANNEL_TYPE_POWER, ANT_SPORT_POWER_PERIOD, ANT_SPORT_POWER_TYPE, ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Power", 'p', ":images/IconPower.png" }, { true, ANTChannel::CHANNEL_TYPE_SPEED, ANT_SPORT_SPEED_PERIOD, ANT_SPORT_SPEED_TYPE, ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Speed", 's', ":images/IconSpeed.png" }, { true, ANTChannel::CHANNEL_TYPE_CADENCE, ANT_SPORT_CADENCE_PERIOD, ANT_SPORT_CADENCE_TYPE, 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_MOXY, ANT_SPORT_MOXY_PERIOD, ANT_SPORT_MOXY_TYPE, ANT_MOXY_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Moxy", 'm', ":images/IconMoxy.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', "" } }; // // The ANT class is a worker thread, reading/writing to a local // Garmin ANT+ serial device. It maintains local state and telemetry. // It is controlled by an ANTController, which starts/stops and will // request telemetry and send commands to assign channels etc // // ANTController sits between the RealtimeWindow and the ANT worker // thread and is part of the GC architecture NOT related to the // hardware controller. // ANT::ANT(QObject *parent, DeviceConfiguration *devConf) : QThread(parent), devConf(devConf) { qRegisterMetaType("ANTMessage"); qRegisterMetaType("uint16_t"); qRegisterMetaType("uint8_t"); qRegisterMetaType("struct timeval"); // device status and settings Status=0; deviceFilename = devConf ? devConf->portSpec : ""; baud=115200; 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; checksum = ANT_SYNC_BYTE; // ant ids - may not be configured of course if (devConf && devConf->deviceProfile.length()) antIDs = devConf->deviceProfile.split(","); else antIDs.clear(); // setup the channels for (int i=0; ichannelValue2(); } double ANT::channelValue(int channel) { return antChannel[channel]->channelValue(); } void ANT::setWheelRpm(float x) { telemetry.setWheelRpm(x); // devConf will be NULL if we are are running the add device wizard // we can default to the global setting if (devConf) telemetry.setSpeed(x * devConf->wheelSize / 1000 * 60 / 1000); else telemetry.setSpeed(x * appsettings->value(NULL, GC_WHEELSIZE, 2100).toInt() / 1000 * 60 / 1000); } void ANT::setHb(double smo2, double thb) { telemetry.setHb(smo2, thb); } /*====================================================================== * Main thread functions; start, stop etc *====================================================================*/ void ANT::run() { int status; // control commands from controller powerchannels = 0; Status = ANT_RUNNING; QString strBuf; #if defined GC_HAVE_LIBUSB usbMode = USBNone; #endif channels = 0; for (int i=0; iinit(); state = ST_WAIT_FOR_SYNC; length = bytes = 0; checksum = ANT_SYNC_BYTE; if (openPort() == 0) { // Moved early setup code (reset, network key, device pairing) to ANT::setup() so that // the receive loop is already running when these early messages are transmitted. This // will enable us to check responses to these messages in the future. } else { quit(0); return; } while(1) { // read more bytes from the device uint8_t byte; if (rawRead(&byte, 1) > 0) receiveByte((unsigned char)byte); else msleep(5); //---------------------------------------------------------------------- // LISTEN TO CONTROLLER FOR COMMANDS //---------------------------------------------------------------------- pvars.lock(); status = this->Status; pvars.unlock(); // do we have a channel to search / stop if (!channelQueue.isEmpty()) { setChannelAtom x = channelQueue.dequeue(); if (x.device_number == -1) antChannel[x.channel]->close(); // unassign else addDevice(x.device_number, x.channel_type, x.channel); // assign } /* time to shut up shop */ if (!(status&ANT_RUNNING)) { // time to stop! quit(0); return; } } } 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); addDevice(0, ANTChannel::CHANNEL_TYPE_MOXY, 5); } } } return 0; } int ANT::restart() { int status; // get current status pvars.lock(); status = this->Status; pvars.unlock(); // what state are we in anyway? if (status&ANT_RUNNING && status&ANT_PAUSED) { status &= ~ANT_PAUSED; pvars.lock(); this->Status = status; pvars.unlock(); return 0; // ok its running again! } return 2; } int ANT::pause() { int status; // get current status pvars.lock(); status = this->Status; pvars.unlock(); if (status&ANT_PAUSED) return 2; else if (!(status&ANT_RUNNING)) return 4; else { // ok we're running and not paused so lets pause status |= ANT_PAUSED; pvars.lock(); this->Status = status; pvars.unlock(); return 0; } } 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()) { unsigned char c = antid.at(antid.length()-1).toLatin1(); int ch_type = interpretSuffix(c); int device_number = antid.mid(0, antid.length()-1).toInt(); removeDevice(device_number, ch_type); } } } // 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! pvars.unlock(); return 0; } int ANT::quit(int code) { // event code goes here! closePort(); // Signal to stop logging. Moved to the end of the reading thread to // ensure no more messages can arrive and re-open the log file. exit(code); return 0; } void ANT::getRealtimeData(RealtimeData &rtData) { rtData = telemetry; rtData.mode = mode; rtData.setLoad(load); rtData.setSlope(gradient); } /*====================================================================== * Channel management *====================================================================*/ // returns -1 for fail otherwise channel number used int ANT::addDevice(int device_number, int device_type, int channel_number) { // if we're given a channel number, then use that one if (channel_number>-1) { //antChannel[channel_number]->close(); antChannel[channel_number]->open(device_number, device_type); return channel_number; } // if we already have the device, then return. // but only if the device number is given since // we may choose to scan for multiple devices // on separate channels (e.g. 0p on channel 0 // and 0p on channel 1 if (device_number != 0) { for (int i=0; ichannel_type == device_type) && (antChannel[i]->device_number == device_number)) { // send the channel found... return i; } } } // look for an unused channel and use on that one for (int i=0; ichannel_type == ANTChannel::CHANNEL_TYPE_UNUSED) { //antChannel[i]->close(); antChannel[i]->open(device_number, device_type); // this is an alternate channel for power if (device_type == ANTChannel::CHANNEL_TYPE_POWER) { // if we are not the first power channel then set to update // the alternate power channel if (powerchannels) antChannel[i]->setAlt(true); // increment the number of power channels powerchannels++; } return i; } } // there are no unused channels. fail. return -1; } // returns 1 for successfully removed, 0 for none found. int ANT::removeDevice(int device_number, int channel_type) { int i; for (i=0; ichannel_type == channel_type) && (ac->device_number == device_number)) { ac->close(); ac->channel_type=ANTChannel::CHANNEL_TYPE_UNUSED; ac->device_number=0; ac->setId(); return 1; } } // device not found. return 0; } ANTChannel * ANT::findDevice(int device_number, int channel_type) { int i; for (i=0; ichannel_type) == channel_type) && (antChannel[i]->device_number==device_number)) { return antChannel[i]; } } // device not found. return NULL; } int ANT::startWaitingSearch() { int i; // are any fast searches in progress? if so, then bail for (i=0; ichannel_type_flags & CHANNEL_TYPE_QUICK_SEARCH) { return 0; } } // start the first slow search for (i=0; ichannel_type_flags & CHANNEL_TYPE_WAITING) { antChannel[i]->channel_type_flags &= ~CHANNEL_TYPE_WAITING; sendMessage(ANTMessage::unassignChannel(i)); return 1; } } return 0; } // For serial device discovery bool ANT::discover(QString name) { #ifdef WIN32 Q_UNUSED(name); #endif #ifdef Q_OS_LINUX // All we can do for USB1 sticks is see if the cp210x driver module // is loaded for this device, and if it is, we will use the device // they are getting rarer and rarer these days (no longer sold by // Garmin anyway) so no need to expend to much energy extending this // especially since the Linux user community is relatively small. struct stat s; if (stat(name.toLatin1(), &s) == -1) return false; int maj = major(s.st_rdev); int min = minor(s.st_rdev); QString sysFile = QString("/sys/dev/char/%1:%2/device/driver/module/drivers/usb:cp210x").arg(maj).arg(min); if (QFileInfo(sysFile).exists()) return true; #endif #ifdef Q_OS_MAC // On MAC we only support the SILabs Virtual Com Port Drivers // which will leave a device file /dev/cu.ANTUSBStick.slabvcp if (name == "/dev/cu.ANTUSBStick.slabvcp") return true; #endif return false; } 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"<> send: "); //for(int i=0; i ANT_MAX_LENGTH)) { state = ST_WAIT_FOR_SYNC; } else { rxMessage[ANT_OFFSET_LENGTH] = byte; checksum ^= byte; length = byte; bytes = 0; state = ST_GET_MESSAGE_ID; } break; case ST_GET_MESSAGE_ID: rxMessage[ANT_OFFSET_ID] = byte; checksum ^= byte; state = ST_GET_DATA; break; case ST_GET_DATA: rxMessage[ANT_OFFSET_DATA + bytes] = byte; checksum ^= byte; if (++bytes >= length){ state = ST_VALIDATE_PACKET; } break; case ST_VALIDATE_PACKET: if (checksum == byte){ processMessage(); } state = ST_WAIT_FOR_SYNC; break; } } // // Pass inbound message to channel for handling // void ANT::handleChannelEvent(void) { int channel = rxMessage[ANT_OFFSET_DATA] & 0x7; if(channel >= 0 && channel < channels) { // handle a channel event here! antChannel[channel]->receiveMessage(rxMessage); } } void ANT::processMessage(void) { ANTMessage m(this, rxMessage); // for debug! //fprintf(stderr, "<< receive: "); //for(int i=0; iclose(); return 0; break; case USB1 : return (int)!CloseHandle(devicePort); break; default : return -1; break; } #else return -1; #endif #else #ifdef GC_HAVE_LIBUSB if (usbMode == USB2) { usb2->close(); return 0; } #endif tcflush(devicePort, TCIOFLUSH); // clear out the garbage return close(devicePort); #endif } bool ANT::find() { #if defined GC_HAVE_LIBUSB if (usb2->find() == true) return true; #ifdef WIN32 if (USBXpress::find() == true) return true; #endif #endif return false; } int ANT::openPort() { #ifdef WIN32 #ifdef GC_HAVE_LIBUSB int rc; // on windows we try on USB2 then on USB1 then fail... if ((rc=usb2->open()) != -1) { usbMode = USB2; channels = 8; return rc; } else if ((rc= USBXpress::open(&devicePort)) != -1) { usbMode = USB1; channels = 4; return rc; } else { usbMode = USBNone; channels = 0; return -1; } #else return -1; #endif #else // LINUX AND MAC USES TERMIO / IOCTL / STDIO #if defined(Q_OS_MACX) int ldisc=TTYDISC; #else int ldisc=N_TTY; // LINUX #endif #ifdef GC_HAVE_LIBUSB int rc; if ((rc=usb2->open()) != -1) { usbMode = USB2; channels = 8; return rc; } usbMode = USB1; #endif // if usb2 failed / not compiled in, we must be using // a USB1 stick so default to 4 channels channels = 4; if ((devicePort=open(deviceFilename.toLatin1(),O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) return errno; tcflush(devicePort, TCIOFLUSH); // clear out the garbage if (ioctl(devicePort, TIOCSETD, &ldisc) == -1) return errno; // get current settings for the port tcgetattr(devicePort, &deviceSettings); // set raw mode i.e. ignbrk, brkint, parmrk, istrip, inlcr, igncr, icrnl, ixon // noopost, cs8, noecho, noechonl, noicanon, noisig, noiexn cfmakeraw(&deviceSettings); cfsetspeed(&deviceSettings, B115200); // further attributes deviceSettings.c_iflag= IGNPAR; deviceSettings.c_oflag=0; deviceSettings.c_cflag &= (~CSIZE & ~CSTOPB); #if defined(Q_OS_MACX) deviceSettings.c_cflag |= (CS8 | CREAD | HUPCL | CCTS_OFLOW | CRTS_IFLOW); #else deviceSettings.c_cflag |= (CS8 | CREAD | HUPCL | CRTSCTS); #endif deviceSettings.c_lflag=0; deviceSettings.c_cc[VMIN]=0; deviceSettings.c_cc[VTIME]=0; // set those attributes if(tcsetattr(devicePort, TCSANOW, &deviceSettings) == -1) return errno; tcgetattr(devicePort, &deviceSettings); #endif // success return 0; } int ANT::rawWrite(uint8_t *bytes, int size) // unix!! { int rc=0; #ifdef WIN32 #ifdef GC_HAVE_LIBUSB switch (usbMode) { case USB1: rc = USBXpress::write(&devicePort, bytes, size); break; case USB2: rc = usb2->write((char *)bytes, size); break; default: rc = 0; break; } if (!rc) rc = -1; // return -1 if nothing written return rc; #else return rc=-1; #endif #else #ifdef GC_HAVE_LIBUSB if (usbMode == USB2) { return usb2->write((char *)bytes, size); } if (usbMode == USB1) { int ibytes; ioctl(devicePort, FIONREAD, &ibytes); // timeouts are less critical for writing, since vols are low rc= write(devicePort, bytes, size); if (rc != -1) tcdrain(devicePort); // wait till its gone. ioctl(devicePort, FIONREAD, &ibytes); return rc; } #endif #endif return -1; } int ANT::rawRead(uint8_t bytes[], int size) { #ifdef WIN32 #ifdef GC_HAVE_LIBUSB switch (usbMode) { case USB1: return USBXpress::read(&devicePort, bytes, size); break; case USB2: return usb2->read((char *)bytes, size); break; default: break; } #else return -1; #endif #else #ifdef GC_HAVE_LIBUSB if (usbMode == USB2) { return usb2->read((char *)bytes, size); } #endif int i=0; uint8_t byte; // read one byte at a time sleeping when no data ready // until we timeout waiting then return error for (i=0; isuffix==c) return st->type; } while (++st, st->type != ANTChannel::CHANNEL_TYPE_GUARD); return -1; } // convert ANT value to 'p' 'c' values char ANT::deviceIdCode(int type) { const ant_sensor_type_t *st=ant_sensor_types; do { if (st->type==type) return st->suffix; } while (++st, st->type != ANTChannel::CHANNEL_TYPE_GUARD); return '-'; } // convert ANT value to 'p' 'c' values char ANT::deviceTypeCode(int type) { const ant_sensor_type_t *st=ant_sensor_types; do { if (st->device_id==type) return st->suffix; } while (++st, st->type != ANTChannel::CHANNEL_TYPE_GUARD); return '-'; } // convert ANT value to human string const char * ANT::deviceTypeDescription(int type) { const ant_sensor_type_t *st=ant_sensor_types; do { if (st->device_id==type) return st->descriptive_name; } while (++st, st->type != ANTChannel::CHANNEL_TYPE_GUARD); return "Unknown device type"; }