void Athlete::updateRideFileIntervals() { // iterate over context->athlete->allIntervals as they are now defined // and update the RideFile->intervals RideItem *which = (RideItem *)treeWidget->selectedItems().first(); RideFile *current = which->ride(); current->clearIntervals(); for (int i=0; i < allIntervals->childCount(); i++) { // add the intervals as updated IntervalItem *it = (IntervalItem *)allIntervals->child(i); current->addInterval(it->start, it->stop, it->text(0)); } // emit signal for interval data changed context->notifyIntervalsChanged(); // set dirty which->setDirty(true); }
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; }
int read_page() { int sum = 0; int bytes_read = 0; char record_command = read_bytes(1, &bytes_read, &sum); // Always 0x89 if ((0xff & record_command) == 0x89) { // 1. page # data int page_number = read_bytes(2, &bytes_read, &sum); // Page # int data_number = read_bytes(1, &bytes_read, &sum); // # of data in page if (page_number == 1 || (page_number == 64010 and secs == 0.0)) { // 2. Training Summary data (60 bytes)"; read_bytes(39, &bytes_read, &sum); int record_training_flag = read_bytes(1, &bytes_read, &sum); // Training Flag if ((record_training_flag & 0x01) == 0) { // Only new lap rideFile->addInterval(last_interval_secs, secs, QString("%1").arg(interval)); last_interval_secs = secs; interval ++; } read_bytes(20, &bytes_read, &sum); // Don't care } if (page_number == 1 || (page_number == 64010 and secs == 0.0)) { // Section Start time and date data (12 byte) int sec = read_bsd_byte(&bytes_read, &sum); // Section start time sec int min = read_bsd_byte(&bytes_read, &sum); // Section start time min int hour = read_bsd_byte(&bytes_read, &sum); // Section start time hour int day = read_bsd_byte(&bytes_read, &sum); // Section start time day int month = read_bytes(1, &bytes_read, &sum); // Section start time month int year = read_bsd_byte(&bytes_read, &sum); // Section start time year QDateTime t = QDateTime(QDate(2000+year,month,day), QTime(hour,min,sec)); if (secs == 0.0 || rideFile->startTime().toTime_t()<0) { rideFile->setStartTime(t); } else { int gap = (t.toTime_t() - rideFile->startTime().toTime_t()) - secs; secs += gap; } read_bytes(5, &bytes_read, &sum); // Don't care read_bytes(1, &bytes_read, &sum); // Data Flag data_number--; } for (int i = 0; i < data_number; ++i) { read_graph_data(&secs, &bytes_read, &sum); } int finish = 259-bytes_read; for (int i = 0; i < finish; i++) { read_bytes(1, &bytes_read, &sum); // to finish } read_bytes(1, &bytes_read, &sum); // Checksum } return bytes_read; }
RideFile *SrmFileReader::openRideFile(QFile &file, QStringList &errorStrings) const { if (!file.open(QFile::ReadOnly)) { errorStrings << QString("can't open file %1").arg(file.fileName()); return NULL; } QDataStream in(&file); RideFile *result = new RideFile; result->setDeviceType("SRM"); char magic[4]; in.readRawData(magic, sizeof(magic)); assert(strncmp(magic, "SRM", 3) == 0); int version = magic[3] - '0'; assert(version == 5 || version == 6 || version == 7); 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); char comment[71]; in.readRawData(comment, sizeof(comment) - 1); comment[commentlen - 1] = '\0'; result->setRecIntSecs(((double) recint1) / recint2); unsigned recintms = (unsigned) round(result->recIntSecs() * 1000.0); 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; } (void) active; (void) avgwatts; (void) avghr; (void) avgcad; (void) avgspeed; (void) pwc150; (void) wheelcirc; } 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); } quint16 zero = readShort(in); quint16 slope = readShort(in); quint16 datacnt = readShort(in); readByte(in); // padding (void) zero; (void) slope; // 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; 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); kph = readLong(in) * 3.6 / 1000.0; alt = readLong(in); double temp = 0.1 * (qint16) readShort(in); (void) temp; // unused for now } 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, 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(); } } double last = 0.0; for (int i = 1; i < markers.size(); ++i) { const marker &marker = markers[i]; int start = marker.start - 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(); result->addInterval(last, start_secs, ""); result->addInterval(start_secs, end_secs, QString("%1").arg(i)); 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; }