diff --git a/src/AllPlot.cpp b/src/AllPlot.cpp index 03454a2ed..a9d65d5f2 100644 --- a/src/AllPlot.cpp +++ b/src/AllPlot.cpp @@ -805,6 +805,7 @@ AllPlot::recalc() setYMax(); refreshIntervalMarkers(); + refreshCalibrationMarkers(); refreshZoneLabels(); //replot(); @@ -843,6 +844,36 @@ AllPlot::refreshIntervalMarkers() } } +void +AllPlot::refreshCalibrationMarkers() +{ + foreach(QwtPlotMarker *mrk, cal_mrk) { + mrk->detach(); + delete mrk; + } + cal_mrk.clear(); + + if (rideItem->ride()) { + foreach(const RideFileCalibration &calibration, rideItem->ride()->calibrations()) { + QwtPlotMarker *mrk = new QwtPlotMarker; + cal_mrk.append(mrk); + mrk->attach(this); + mrk->setLineStyle(QwtPlotMarker::VLine); + mrk->setLabelAlignment(Qt::AlignRight | Qt::AlignTop); + mrk->setLinePen(QPen(GColor(CCALIBRATIONMARKER), 0, Qt::DashDotLine)); + QwtText text("\n\n"+calibration.name); + text.setFont(QFont("Helvetica", 9, QFont::Bold)); + text.setColor(GColor(CCALIBRATIONMARKER)); + if (!bydist) + mrk->setValue(calibration.start / 60.0, 0.0); + else + mrk->setValue((context->athlete->useMetricUnits ? 1 : MILES_PER_KM) * + rideItem->ride()->timeToDistance(calibration.start), 0.0); + mrk->setLabel(text); + } + } +} + void AllPlot::setYMax() { @@ -1180,6 +1211,7 @@ AllPlot::setDataFromPlot(AllPlot *plot, int startidx, int stopidx) refreshIntervalMarkers(); + refreshCalibrationMarkers(); refreshZoneLabels(); //if (this->legend()) this->legend()->show(); diff --git a/src/AllPlot.h b/src/AllPlot.h index 8a4ef5fbf..1a0b50b70 100644 --- a/src/AllPlot.h +++ b/src/AllPlot.h @@ -61,6 +61,7 @@ class AllPlot : public QwtPlot bool shadeZones() const; void refreshZoneLabels(); void refreshIntervalMarkers(); + void refreshCalibrationMarkers(); void setAxisTitle(int axis, QString label); // refresh data / plot parameters @@ -116,6 +117,7 @@ class AllPlot : public QwtPlot // plot objects QwtPlotGrid *grid; QVector d_mrk; + QVector cal_mrk; QwtPlotMarker *allMarker1; QwtPlotMarker *allMarker2; QwtPlotCurve *wattsCurve; diff --git a/src/Colors.cpp b/src/Colors.cpp index 697079502..94b9cfcdf 100644 --- a/src/Colors.cpp +++ b/src/Colors.cpp @@ -110,6 +110,7 @@ void GCColor::setupColors() { tr("Alternate Power"), "CALTPOWER", Qt::magenta }, { tr("Left Balance"), "CBALANCELEFT", QColor(178,0,0) }, { tr("Right Balance"), "CBALANCERIGHT", QColor(128,0,50) }, + { tr("Plot Markers"), "COLORCALIBRATIONMARKER", QColor(40,40,40) }, { "", "", QColor(0,0,0) }, }; diff --git a/src/Colors.h b/src/Colors.h index 4e85b0ceb..85fc8891b 100644 --- a/src/Colors.h +++ b/src/Colors.h @@ -99,7 +99,7 @@ class ColorEngine : public QObject #define GColor(x) GCColor::getColor(x) // Define how many cconfigurable metric colors are available -#define CNUMOFCFGCOLORS 70 +#define CNUMOFCFGCOLORS 71 #define CPLOTBACKGROUND 0 #define CRIDEPLOTBACKGROUND 1 @@ -171,5 +171,6 @@ class ColorEngine : public QObject #define CALTPOWER 67 #define CBALANCELEFT 68 #define CBALANCERIGHT 69 +#define CCALIBRATIONMARKER 70 #endif diff --git a/src/FitRideFile.cpp b/src/FitRideFile.cpp index 0f0feece1..7c04bc388 100644 --- a/src/FitRideFile.cpp +++ b/src/FitRideFile.cpp @@ -68,6 +68,7 @@ struct FitFileReaderState QMap local_msg_types; QSet unknown_record_fields, unknown_global_msg_nums, unknown_base_type; int interval; + int calibration; int devices; bool stopped; int last_event_type; @@ -76,7 +77,7 @@ struct FitFileReaderState FitFileReaderState(QFile &file, QStringList &errors) : file(file), errors(errors), rideFile(NULL), start_time(0), - last_time(0), last_distance(0.00f), interval(0), devices(0), stopped(true), + last_time(0), last_distance(0.00f), interval(0), calibration(0), devices(0), stopped(true), last_event_type(-1), last_event(-1), last_msg_type(-1) { } @@ -251,8 +252,10 @@ struct FitFileReaderState } void decodeEvent(const FitDefinition &def, int, const std::vector values) { + int time = -1; int event = -1; int event_type = -1; + qint16 data16 = -1; int i = 0; foreach(const FitField &field, def.fields) { fit_value_t value = values[i++]; @@ -261,10 +264,15 @@ struct FitFileReaderState continue; switch (field.num) { - case 253: //time = value + qbase_time.toTime_t(); + case 253: // timestamp field (s) + time = value + qbase_time.toTime_t(); break; - case 0: event = value; break; - case 1: event_type = value; break; + case 0: // event field + event = value; break; + case 1: // event_type field + event_type = value; break; + case 2: // data16 field + data16 = value; break; default: ; // do nothing } } @@ -292,7 +300,19 @@ struct FitFileReaderState stopped = true; break; default: - errors << QString("Unknown event type %1").arg(event_type); + errors << QString("Unknown timer event type %1").arg(event_type); + } + } + else if (event == 36) { // Calibration event + int secs = (start_time==0?0:time-start_time); + switch (event_type) { + case 3: // marker + ++calibration; + rideFile->addCalibration(secs, data16, QString("Calibration %1 (%2)").arg(calibration).arg(data16)); + qDebug() << "marker" << secs << data16; + break; + default: + errors << QString("Unknown calibration event type %1").arg(event_type); } } // printf("event type %d\n", event_type); @@ -375,8 +395,10 @@ struct FitFileReaderState case 13: temperature = value; break; case 29: // ACCUMULATED_POWER break; - case 30: lrbalance = (value & 0x80 ? 100 - (value & 0x7F) : value & 0x7F);break; - + case 30: lrbalance = (value & 0x80 ? 100 - (value & 0x7F) : value & 0x7F); + break; + case 31: // GPS Accuracy + break; default: unknown_record_fields.insert(field.num); } } diff --git a/src/JsonRideFile.l b/src/JsonRideFile.l index b3146a663..cee32ffcd 100644 --- a/src/JsonRideFile.l +++ b/src/JsonRideFile.l @@ -51,6 +51,8 @@ \"NAME\" return NAME; \"START\" return START; \"STOP\" return STOP; +\"CALIBRATIONS\" return CALIBRATIONS; +\"VALUE\" return VALUE; \"REFERENCES\" return REFERENCES; \"SAMPLES\" return SAMPLES; \"SECS\" return SECS; diff --git a/src/JsonRideFile.y b/src/JsonRideFile.y index 58c8b5dd9..73dd0d9f3 100644 --- a/src/JsonRideFile.y +++ b/src/JsonRideFile.y @@ -43,6 +43,7 @@ static RideFile *JsonRide; // term state data is held in these variables static RideFilePoint JsonPoint; static RideFileInterval JsonInterval; +static RideFileCalibration JsonCalibration; static QString JsonString, JsonTagKey, JsonTagValue, JsonOverName, JsonOverKey, JsonOverValue; @@ -108,6 +109,7 @@ static QString unprotect(const QString string) %token RIDE STARTTIME RECINTSECS DEVICETYPE IDENTIFIER %token OVERRIDES %token TAGS INTERVALS NAME START STOP +%token CALIBRATIONS VALUE %token REFERENCES %token SAMPLES SECS KM WATTS NM CAD KPH HR ALTITUDE LAT LON HEADWIND SLOPE TEMP LRBALANCE @@ -136,6 +138,7 @@ rideelement: starttime | overrides | tags | intervals + | calibrations | references | samples ; @@ -194,6 +197,22 @@ interval: '{' NAME ':' string ',' { JsonInterval.name = JsonString; } JsonInterval = RideFileInterval(); } +/* + * Calibrations + */ +calibrations: CALIBRATIONS ':' '[' calibration_list ']' ; +calibration_list: calibration | calibration_list ',' calibration ; +calibration: '{' NAME ':' string ',' { JsonCalibration.name = JsonString; } + START ':' number ',' { JsonCalibration.start = JsonNumber; } + VALUE ':' number { JsonCalibration.value = JsonNumber; } + '}' + { JsonRide->addCalibration(JsonCalibration.start, + JsonCalibration.value, + JsonCalibration.name); + JsonCalibration = RideFileCalibration(); + } + + /* * Ride references */ @@ -403,6 +422,26 @@ JsonFileReader::writeRideFile(Context *, const RideFile *ride, QFile &file) cons out <<"\n\t\t]"; } + // + // CALIBRATION + // + if (!ride->calibrations().empty()) { + + out << ",\n\t\t\"CALIBRATIONS\":[\n"; + bool first = true; + + foreach (RideFileCalibration i, ride->calibrations()) { + if (first) first=false; + else out << ",\n"; + + out << "\t\t\t{ "; + out << "\"NAME\":\"" << protect(i.name) << "\""; + out << ", \"START\": " << QString("%1").arg(i.start); + out << ", \"VALUE\": " << QString("%1").arg(i.value) << " }"; + } + out <<"\n\t\t]"; + } + // // SAMPLES // diff --git a/src/RideFile.h b/src/RideFile.h index 5f00a375e..c9403c5ba 100644 --- a/src/RideFile.h +++ b/src/RideFile.h @@ -74,6 +74,19 @@ struct RideFileInterval bool operator< (RideFileInterval right) const { return start < right.start; } }; +struct RideFileCalibration +{ + double start; + int value; + QString name; + RideFileCalibration() : start(0.0), value(0) {} + RideFileCalibration(double start, int value, QString name) : + start(start), value(value), name(name) {} + + // order bu start time + bool operator< (RideFileCalibration right) const { return start < right.start; } +}; + class RideFile : public QObject // QObject to emit signals { Q_OBJECT @@ -145,6 +158,12 @@ class RideFile : public QObject // QObject to emit signals void fillInIntervals(); int intervalBegin(const RideFileInterval &interval) const; + // Working with CAIBRATIONS + const QList &calibrations() const { return calibrations_; } + void addCalibration(double start, int value, const QString &name) { + calibrations_.append(RideFileCalibration(start, value, name)); + } + // Index offset calculations double timeToDistance(double) const; // get distance in km at time in secs int timeIndex(double) const; // get index offset for time in secs @@ -206,6 +225,7 @@ class RideFile : public QObject // QObject to emit signals QString deviceType_; QString fileFormat_; QList intervals_; + QList calibrations_; QMap tags_; EditorData *data; double weight_; // cached to save calls to getWeight();