/* * 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 #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 }; // supported sensor types const ant_sensor_type_t ANT::ant_sensor_types[] = { { ANTChannel::CHANNEL_TYPE_UNUSED, 0, 0, 0, 0, "Unused", '?', "" }, { ANTChannel::CHANNEL_TYPE_HR, ANT_SPORT_HR_PERIOD, ANT_SPORT_HR_TYPE, ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Heartrate", 'h', ":images/IconHR.png" }, { ANTChannel::CHANNEL_TYPE_POWER, ANT_SPORT_POWER_PERIOD, ANT_SPORT_POWER_TYPE, ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Power", 'p', ":images/IconPower.png" }, { ANTChannel::CHANNEL_TYPE_SPEED, ANT_SPORT_SPEED_PERIOD, ANT_SPORT_SPEED_TYPE, ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Speed", 's', ":images/IconSpeed.png" }, { ANTChannel::CHANNEL_TYPE_CADENCE, ANT_SPORT_CADENCE_PERIOD, ANT_SPORT_CADENCE_TYPE, ANT_SPORT_FREQUENCY, ANT_SPORT_NETWORK_NUMBER, "Cadence", 'c', ":images/IconCadence.png" }, { 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" }, // We comment out these Quarq specials -- they appear to be experimental // but kept in the code incase they re-appear :) #if 0 { ANTChannel::CHANNEL_TYPE_QUARQ, ANT_QUARQ_PERIOD, ANT_QUARQ_TYPE, ANT_QUARQ_FREQUENCY, DEFAULT_NETWORK_NUMBER, "Quarq Channel", 'Q', ":images/IconPower.png" }, { ANTChannel::CHANNEL_TYPE_FAST_QUARQ, ANT_FAST_QUARQ_PERIOD, ANT_FAST_QUARQ_TYPE, ANT_FAST_QUARQ_FREQUENCY, DEFAULT_NETWORK_NUMBER, "Fast Quarq", 'q', ":images/IconPower.png" }, { ANTChannel::CHANNEL_TYPE_FAST_QUARQ_NEW, ANT_FAST_QUARQ_PERIOD, ANT_FAST_QUARQ_TYPE_WAS, ANT_FAST_QUARQ_FREQUENCY, DEFAULT_NETWORK_NUMBER, "Fast Quarq New", 'n', ":images/IconPower.png" }, #endif { 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) { // device status and settings Status=0; deviceFilename = devConf ? devConf->portSpec : ""; baud=115200; powerchannels=0; configuring = false; // 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); else telemetry.setSpeed((x * devConf->wheelSize / 1000) * 60 / 1000); // not a global setting anymore -- device specific } /*====================================================================== * 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) { sendMessage(ANTMessage::resetSystem()); sendMessage(ANTMessage::setNetworkKey(1, key)); // pair with specified devices on next available channel 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(); addDevice(device_number, ch_type, -1); } } } else { if (!configuring) { // not configured, just pair with whatever you can find addDevice(0, ANTChannel::CHANNEL_TYPE_SPEED, 0); addDevice(0, ANTChannel::CHANNEL_TYPE_POWER, 1); addDevice(0, ANTChannel::CHANNEL_TYPE_CADENCE, 2); addDevice(0, ANTChannel::CHANNEL_TYPE_HR, 3); if (channels > 4) addDevice(0, ANTChannel::CHANNEL_TYPE_SandC, 4); } } } 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; } } } int ANT::start() { QThread::start(); 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() { // what state are we in anyway? pvars.lock(); Status = 0; // Terminate it! pvars.unlock(); // Signal to stop logging emit receivedAntMessage(NULL, NULL); return 0; } int ANT::quit(int code) { // event code goes here! closePort(); exit(code); return 0; } 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); } /*====================================================================== * Channel management *====================================================================*/ // returns 1 for success, 0 for fail. 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 1; } // 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 & 0xf ) == device_type) && (antChannel[i]->device_number == device_number)) { // send the channel found... //XXX antChannel[i]->channelInfo(); return 1; } } } // 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 1; } } // there are no unused channels. fail. return 0; } // 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)) { 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; 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; } void ANT::report() { for (int i=0; ichannelInfo(); ; } 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) { #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 // XXX need a better way of probing this device, but USB1 sticks // are getting rarer, so maybe we can just make do with this // until we deprecate them altogether 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) { emit foundDevice(channel, device_number, device_id); //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 #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 WIN32 || defined GC_HAVE_LIBUSB if (usb2->find() == true) return true; #endif #ifdef WIN32 if (USBXpress::find() == true) return true; #endif return false; } int ANT::openPort() { #ifdef WIN32 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 // 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.toAscii(),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 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 #ifdef GC_HAVE_LIBUSB if (usbMode == USB2) { return usb2->write((char *)bytes, size); } #endif 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 } int ANT::rawRead(uint8_t bytes[], int size) { int rc=0; #ifdef WIN32 switch (usbMode) { case USB1: return USBXpress::read(&devicePort, bytes, size); break; case USB2: return usb2->read((char *)bytes, size); break; default: rc = 0; break; } #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 // XXX this and below are named wrong, legacy from quarqd code. 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"; }