示例#1
0
    RideFile * run() {
        errors.clear();
        rideFile = new RideFile;
        rideFile->setDeviceType("Joule GPS");
        rideFile->setFileFormat("CycleOps Joule (bin2)");
        rideFile->setRecIntSecs(1);

        if (!file.open(QIODevice::ReadOnly)) {
            delete rideFile;
            return NULL;
        }
        bool stop = false;

        int data_size = file.size();
        int bytes_read = 0;

        bytes_read += read_version();
        bytes_read += read_system_info();
        bytes_read += read_summary_page();

        while (!stop && (bytes_read < data_size)) {
            bytes_read += read_detail_page(); // read_page(stop, errors);
        }

        rideFile->setTag("Device Info", deviceInfo);

        if (stop) {
            delete rideFile;
            return NULL;
        }
        else {
            return rideFile;
        }
    }
示例#2
0
RideFile *SrmFileReader::openRideFile(QFile &file, QStringList &errorStrings, QList<RideFile*>*) const
{
    if (!file.open(QFile::ReadOnly)) {
        errorStrings << QString("can't open file %1").arg(file.fileName());
        return NULL;
    }
    QDataStream in(&file);
    in.setByteOrder( QDataStream::LittleEndian );

    RideFile *result = new RideFile;
    result->setDeviceType("SRM");
    result->setFileFormat("SRM training files (srm)");
    result->setTag("Sport", "Bike" );

    char magic[4];
    in.readRawData(magic, sizeof(magic));
    if( strncmp(magic, "SRM", 3)){
        errorStrings << QString("Unrecognized file type, missing magic." );
        return NULL;
    }

    int version = magic[3] - '0';
    switch( version ){
      case 5:
      case 6:
      case 7:
        // ok
        break;

      default:
        errorStrings << QString("Unsupported SRM file format version: %1")
            .arg(version);
        return NULL;
    }

    quint16 dayssince1880 = readShort(in);
    quint16 wheelcirc = readShort(in);
    quint8 recint1 = readByte(in);
    quint8 recint2 = readByte(in);
    quint16 blockcnt = readShort(in);
    quint16 markercnt = readShort(in);
    readByte(in); // padding
    quint8 commentlen = readByte(in);

    if( commentlen > 70 )
        commentlen = 70;

    char comment[71];
    in.readRawData(comment, sizeof(comment) - 1);
    comment[commentlen] = '\0';
    result->setTag("Notes", QString(comment) );

    // assert propper markercnt to avoid segfaults
    if( in.status() != QDataStream::Ok ){
        errorStrings << QString("failed to read file header" );
        return NULL;
    }

    result->setRecIntSecs(((double) recint1) / recint2);
    unsigned recintms = (unsigned) round(result->recIntSecs() * 1000.0);

    result->setTag("Wheel Circumference", QString("%1").arg(wheelcirc) );

    QDate date(1880, 1, 1);
    date = date.addDays(dayssince1880);

    QVector<marker> markers(markercnt + 1);
    for (int i = 0; i <= markercnt; ++i) {
        char mcomment[256];
        size_t mcommentlen = version < 6 ? 3 : 255;
        assert( mcommentlen < sizeof(mcomment) );
        in.readRawData(mcomment, mcommentlen );
        mcomment[mcommentlen] = '\0';

        quint8 active = readByte(in);
        quint16 start = readShort(in);
        quint16 end = readShort(in);
        quint16 avgwatts = readShort(in);
        quint16 avghr = readShort(in);
        quint16 avgcad = readShort(in);
        quint16 avgspeed = readShort(in);
        quint16 pwc150 = readShort(in);

    // data fixup: Although the data chunk index in srm files starts
    // with 1, some srmwin wrote files referencing index 0.
    if( end < 1 ) end = 1;
    if( start < 1 ) start = 1;

    // data fixup: some srmwin versions wrote markers with start > end
    if( end < start ){
        markers[i].start = end;
        markers[i].end = start;
    } else {
        markers[i].start = start;
        markers[i].end = end;
    }

        markers[i].note = QString( mcomment);

        if( i == 0 ){
            result->setTag("Athlete Name", QString(mcomment) );
        }

        (void) active;
        (void) avgwatts;
        (void) avghr;
        (void) avgcad;
        (void) avgspeed;
        (void) pwc150;
        (void) wheelcirc;
    }

    // fail early to tell devs whats wrong with file
    if( in.status() != QDataStream::Ok ){
        errorStrings << QString("failed to read marker" );
        return NULL;
    }

    blockhdr *blockhdrs = new blockhdr[blockcnt+1];
    for (int i = 0; i < blockcnt; ++i) {
        // In the .srm files generated by Rainer Clasen's srmcmd,
        // hsecsincemidn is a *signed* 32-bit integer.  I haven't seen a
        // negative value in any .srm files generated by srmwin.exe, but
        // since the number of hundredths of a second in a day is << 2^31,
        // it seems safe to always treat this number as signed.
        qint32 hsecsincemidn = readLong(in);
        blockhdrs[i].chunkcnt = readShort(in);
        blockhdrs[i].dt = QDateTime(date);
        blockhdrs[i].dt = blockhdrs[i].dt.addMSecs(hsecsincemidn * 10);
    }

    // fail early to tell devs whats wrong with file
    if( in.status() != QDataStream::Ok ){
        errorStrings << QString("failed to read block headers" );
        return NULL;
    }

    quint16 zero = readShort(in);
    quint16 slope = readShort(in);
    quint16 datacnt = readShort(in);
    readByte(in); // padding

    // fail early to tell devs whats wrong with file
    if( in.status() != QDataStream::Ok ){
        errorStrings << QString("failed to read calibration data" );
        return NULL;
    }

    result->setTag("Slope", QString("%1")
        .arg( 140.0 / 42781 * slope, 0, 'f', 2) );
    result->setTag("Zero Offset", QString("%1").arg(zero) );

    // SRM5 files have no blocks - synthesize one
    if( blockcnt < 1 ){
        blockcnt = 0;
        blockhdrs[0].chunkcnt = datacnt;
        blockhdrs[0].dt = QDateTime(date);
    }


    int blknum = 0, blkidx = 0, mrknum = 0, interval = 0;
    double km = 0.0, secs = 0.0;

    if (markercnt > 0)
        mrknum = 1;

    for (int i = 0; i < datacnt; ++i) {
        int cad, hr, watts;
        double kph, alt;
        double temp=-255;
        if (version < 7) {
            quint8 ps[3];
            in.readRawData((char*) ps, sizeof(ps));
            cad = readByte(in);
            hr = readByte(in);
            kph = (((((unsigned) ps[1]) & 0xf0) << 3)
                   | (ps[0] & 0x7f)) * 3.0 / 26.0;
            watts = (ps[1] & 0x0f) | (ps[2] << 0x4);
            alt = 0.0;
        }
        else {
            assert(version == 7);
            watts = readShort(in);
            cad = readByte(in);
            hr = readByte(in);

            qint32 kph_tmp = readSignedLong(in);
            kph = kph_tmp < 0 ? 0 : kph_tmp * 3.6 / 1000.0;

            alt = readSignedLong(in);
            temp = 0.1 * readSignedShort(in);
        }

        if (i == 0) {
            result->setStartTime(blockhdrs[blknum].dt);
        }
        if (mrknum < markers.size() && i == markers[mrknum].end) {
            ++interval;
            ++mrknum;
        }

        // markers count from 1
        if ((i > 0) && (mrknum < markers.size()) && (i == markers[mrknum].start - 1))
            ++interval;

        km += result->recIntSecs() * kph / 3600.0;

        double nm = watts / 2.0 / PI / cad * 60.0;
        result->appendPoint(secs, cad, hr, km, kph, nm, watts, alt, 0.0, 0.0, 0.0, 0.0, temp, 0.0, interval);

        ++blkidx;
        if ((blkidx == blockhdrs[blknum].chunkcnt) && (blknum + 1 < blockcnt)) {
            QDateTime end = blockhdrs[blknum].dt.addMSecs(
                recintms * blockhdrs[blknum].chunkcnt);
            ++blknum;
            blkidx = 0;
            QDateTime start = blockhdrs[blknum].dt;
            qint64 endms =
                ((qint64) end.toTime_t()) * 1000 + end.time().msec();
            qint64 startms =
                ((qint64) start.toTime_t()) * 1000 + start.time().msec();
            double diff_secs = (startms - endms) / 1000.0;
            if (diff_secs < result->recIntSecs()) {
                errorStrings << QString("ERROR: time goes backwards by %1 s"
                                        " on trans " "to block %2"
                                        ).arg(diff_secs).arg(blknum);
                secs += result->recIntSecs(); // for lack of a better option
            }
            else {
                secs += diff_secs;
            }
        }
        else {
            secs += result->recIntSecs();
        }
    }

    // assert some points were found. prevents segfault when looking at
    // the overall markers[0].start/.end
    // note: we're not checking in.status() to cope with truncated files

    if( result->dataPoints().size() < 1 ){
        errorStrings << QString("file contains no data points");
        return NULL;
    }

    double last = 0.0;
    for (int i = 1; i < markers.size(); ++i) {
        const marker &marker = markers[i];
        int start = qMin(marker.start, result->dataPoints().size()) - 1;
        double start_secs = result->dataPoints()[start]->secs;
        int end = qMin(marker.end - 1, result->dataPoints().size() - 1);
        double end_secs = result->dataPoints()[end]->secs + result->recIntSecs();
        if( last < start_secs )
            result->addInterval(last, start_secs, "");
        QString note = QString("%1").arg(i);
        if( marker.note.length() )
            note += QString(" ") + marker.note;
        if( start_secs <= end_secs )
            result->addInterval(start_secs, end_secs, note );
        last = end_secs;
    }
    if (!markers.empty() && markers.last().end < result->dataPoints().size()) {
        double start_secs = result->dataPoints().last()->secs + result->recIntSecs();
        result->addInterval(last, start_secs, "");
    }

    file.close();
    return result;
}
示例#3
0
RideFile *PolarFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*rideList) const
{
/*
* Polar HRM file format documented at www.polar.fi/files/Polar_HRM_file%20format.pdf
*/
    QRegExp metricUnits("(km|kph|km/h)", Qt::CaseInsensitive);
    QRegExp englishUnits("(miles|mph|mp/h)", Qt::CaseInsensitive);
    bool metric = true;

    QDate date;
    QString note("");

    double version=0;
    int monitor=0;

    double seconds=0;
    double distance=0;
    int interval = 0;
    int StartDelay = 0;

    bool speed = false;
    bool cadence = false;
    bool altitude = false;
    bool power = false;
    bool balance = false;
    bool haveGPX = false;
    int igpx = 0;
    int ngpx = 0;
    double lat=0,lon=0;
    int recInterval = 1;

    // Read Polar GPX file (if exist with same name as hrm file).
    RideFile *gpxresult=NULL;
    RideFilePoint *p;
    QString suffix = file.fileName();
    int dot = suffix.lastIndexOf(".");
    assert(dot >= 0);
    QFile gpxfile(suffix.left(dot)+".gpx");
    haveGPX = gpxfile.exists();

    if (haveGPX) {
        GpxFileReader reader;
        gpxresult = reader.openRideFile(gpxfile,errors,rideList);
        ngpx = gpxresult->dataPoints().count();
    }

    if (!file.open(QFile::ReadOnly)) {
        errors << ("Could not open ride file: \""
                   + file.fileName() + "\"");
        return NULL;
    }

    int lineno = 1;


    double next_interval=0;
    QList<double> intervals;

    QTextStream is(&file);
    RideFile *rideFile = new RideFile();
    QString section = NULL;

    while (!is.atEnd()) {
        // the readLine() method doesn't handle old Macintosh CR line endings
        // this workaround will load the the entire file if it has CR endings
        // then split and loop through each line
        // otherwise, there will be nothing to split and it will read each line as expected.
        QString linesIn = is.readLine();
        QStringList lines = linesIn.split('\r');
        // workaround for empty lines
        if(lines.size() == 0) {
            lineno++;
            continue;
        }
        for (int li = 0; li < lines.size(); ++li) {
            QString line = lines[li];

            if (line == "") {

            }
            else if (line.startsWith("[")) {
                //fprintf(stderr, "section : %s\n", line.toAscii().constData());
                section=line;
                if (section == "[HRData]") {
                    // Some systems, like the Tacx HRM exporter, do not add an [IntTimes] section, so we need to
                    // specify that the whole ride is one big interval.
                    if (intervals.isEmpty())
                        intervals.append(seconds);
                   next_interval = intervals.at(0);
               }
            }
            else if (section == "[Params]"){
                if (line.contains("Version=")) {
                    QString versionString = QString(line);
                    versionString.remove(0,8).insert(1, ".");
                    version = versionString.toFloat();
                    rideFile->setFileFormat("Polar HRM v"+versionString+" (hrm)");
                } else if (line.contains("Monitor=")) {
                    QString monitorString = QString(line);
                    monitorString.remove(0,8);
                    monitor = monitorString.toInt();
                    switch (monitor) {
                        case 1: rideFile->setDeviceType("Polar Sport Tester / Vantage XL"); break;
                        case 2: rideFile->setDeviceType("Polar Vantage NV (VNV)"); break;
                        case 3: rideFile->setDeviceType("Polar Accurex Plus"); break;
                        case 4: rideFile->setDeviceType("Polar XTrainer Plus"); break;
                        case 6: rideFile->setDeviceType("Polar S520"); break;
                        case 7: rideFile->setDeviceType("Polar Coach"); break;
                        case 8: rideFile->setDeviceType("Polar S210"); break;
                        case 9: rideFile->setDeviceType("Polar S410"); break;
                        case 10: rideFile->setDeviceType("Polar S510"); break;
                        case 11: rideFile->setDeviceType("Polar S610 / S610i"); break;
                        case 12: rideFile->setDeviceType("Polar S710 / S710i"); break;
                        case 13: rideFile->setDeviceType("Polar S810 / S810i"); break;
                        case 15: rideFile->setDeviceType("Polar E600"); break;
                        case 20: rideFile->setDeviceType("Polar AXN500"); break;
                        case 21: rideFile->setDeviceType("Polar AXN700"); break;
                        case 22: rideFile->setDeviceType("Polar S625X / S725X"); break;
                        case 23: rideFile->setDeviceType("Polar S725"); break;
                        case 33: rideFile->setDeviceType("Polar CS400"); break;
                        case 34: rideFile->setDeviceType("Polar CS600X"); break;
                        case 35: rideFile->setDeviceType("Polar CS600"); break;
                        case 36: rideFile->setDeviceType("Polar RS400"); break;
                        case 37: rideFile->setDeviceType("Polar RS800"); break;
                        case 38: rideFile->setDeviceType("Polar RS800X"); break;

                        default: rideFile->setDeviceType(QString("Unknown Polar Device %1").arg(monitor));
                   }
                } else if (line.contains("SMode=")) {
                    line.remove(0,6);
                    QString smode = QString(line);
                    if (smode.at(0)=='1')
                        speed = true;
                    if (smode.length()>0 && smode.at(1)=='1')
                        cadence = true;
                    if (smode.length()>1 && smode.at(2)=='1')
                        altitude = true;
                    if (smode.length()>2 && smode.at(3)=='1')
                        power = true;
                    if (smode.length()>3 && smode.at(4)=='1')
                        balance = true;
                    //if (smode.length()>4 && smode.at(5)=='1') pedaling_index = true;

                    //
                    // It appears that the Polar CS600 exports its data alays in metric when downloaded from the
                    // polar software even when English units are displayed on the unit..  It also never sets
                    // this bit low in the .hrm file.  This will have to get changed if other software downloads
                    // this differently
                    //

                    if (smode.length()>6 && smode.at(7)=='1')
                        metric = false;

                } else if (line.contains("Interval=")) {

                    recInterval = line.remove(0,9).toInt();

                    if (recInterval==238) {

                        /* This R-R data */
                        rideFile->setRecIntSecs(1);

                    } else {

                        rideFile->setRecIntSecs(recInterval);
                    }

                } else if (line.contains("Date=")) {
                    line.remove(0,5);
                    date= QDate(line.left(4).toInt(),
                                line.mid(4,2).toInt(),
                                line.mid(6,2).toInt());
                } else if (line.contains("StartTime=")) {
                    line.remove(0,10);
                    QDateTime datetime(date,
                                      QTime(line.left(2).toInt(),
                                            line.mid(3,2).toInt(),
                                            line.mid(6,2).toInt()));
                    rideFile->setStartTime(datetime);

                } else if (line.contains("StartDelay=")) {

                    StartDelay = line.remove(0,11).toInt();

                    if (recInterval==238) {
                        seconds = StartDelay/1000.0;
                    } else {
                        seconds = recInterval;
                    }
                }

            } else if (section == "[Note]") {

                note.append(line);

            } else if (section == "[IntTimes]") {

                double int_seconds = line.left(2).toInt()*60*60+line.mid(3,2).toInt()*60+line.mid(6,3).toFloat();
                intervals.append(int_seconds);

                if (lines.size()==1) {
                   is.readLine();
                   is.readLine();
                   if (version>1.05) {
                       is.readLine();
                       is.readLine();
                   }
                } else {
                   li+=2;
                   if (version>1.05)
                      li+=2;
                }

            } else if (section == "[HRData]") {

                double nm=0,kph=0,watts=0,km=0,cad=0,hr=0,alt=0,hrm=0;
                double lrbalance=RideFile::NA;

                int i=0;
                hrm = line.section('\t', i, i).toDouble();
                i++;

                if (speed) {
                    kph = line.section('\t', i, i).toDouble()/10;
                    distance += kph/60/60*recInterval;
                    km = distance;
                    i++;
                }
                if (cadence) {
                    cad = line.section('\t', i, i).toDouble();
                    i++;
                }
                if (altitude) {
                    alt = line.section('\t', i, i).toDouble();
                    i++;
                }
                if (power) {
                    watts = line.section('\t', i, i).toDouble();
                    i++;
                }
                if (balance) {
                    // Power LRB + PI:  The value contains :
                    //  - Left Right Balance (LRB) and
                    //  - Pedaling Index (PI)
                    //
                    // in the following formula:
                    // value = PI * 256 + LRB   PI bits 15-8  LRB bits 7-0
                    // LRB is the value of left foot
                    // for example if LRB = 45, actual balance is L45 - 55R.
                    // PI values are percentages from 0 to 100.
                    // For example value 12857 (= 40 * 256 + 47)
                    // means: PI = 40 and LRB = 47 => L47 - 53R

                    lrbalance = line.section('\t', i, i).toInt() & 0xff;
                    i++;
                }

                if (next_interval < seconds) {
                    interval = intervals.indexOf(next_interval);
                    if (intervals.count()>interval+1){
                        interval++;
                        next_interval = intervals.at(interval);
                    }
                }

                if (!metric) {
                    km *= KM_PER_MILE;
                    kph *= KM_PER_MILE;
                    alt *= METERS_PER_FOOT;
                }

                if (recInterval==238){
                    hr = 60000.0/hrm;
                } else {
                    hr = hrm;
                }

                if (haveGPX && gpxresult && (igpx<ngpx)) {

                    p = gpxresult->dataPoints()[igpx];

                    // Use previous value if GPS is momentarely
                    // lost. Should have option for interpolating.
                    if (p->lat!=0.0 && p->lon!=0.0) {

                        lat = p->lat;
                        lon = p->lon;

                        // Must check if current HRM speed is zero while
                        // we have GPX speed
                        if (kph==0.0 && p->kph>1.0) {

                            kph = p->kph;
                            distance += kph/60/60*recInterval;
                            km = distance;
                        }
                    }

                    if (seconds>=p->secs) igpx += 1;
                }

                rideFile->appendPoint(seconds, cad, hr, km, kph, nm, watts, alt, lon, lat,
                                      0.0, 0.0,
                                      RideFile::NA, lrbalance,
                                      0.0, 0.0, 0.0, 0.0,
                                      0.0, 0.0,
                                      0.0, 0.0, 0.0, 0.0,
                                      0.0, 0.0, 0.0, 0.0,
                                      0.0, 0.0,
                                      0.0, 0.0, 0.0, 0.0,
                                      interval);

                // fprintf(stderr, " %f, %f, %f, %f, %f, %f, %f, %d\n", seconds, cad, hr, km, kph, nm, watts, alt, interval);
                if (recInterval==238) {
                    seconds += hrm / 1000.0;
                } else {
                    seconds += recInterval;
                }
            }

            ++lineno;
        }
    }

    rideFile->setTag("Notes", note);
    QRegExp rideTime("^.*/(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)_"
                     "(\\d\\d)_(\\d\\d)_(\\d\\d)\\.hrm$");
    if (rideTime.indexIn(file.fileName()) >= 0) {
        QDateTime datetime(QDate(rideTime.cap(1).toInt(),
                                 rideTime.cap(2).toInt(),
                                 rideTime.cap(3).toInt()),
                           QTime(rideTime.cap(4).toInt(),
                                 rideTime.cap(5).toInt(),
                                 rideTime.cap(6).toInt()));


        rideFile->setStartTime(datetime);
    }
    file.close();

    return rideFile;
}