diff --git a/src/ANT/ANT.cpp b/src/ANT/ANT.cpp index 79ca81a97..944197a3c 100644 --- a/src/ANT/ANT.cpp +++ b/src/ANT/ANT.cpp @@ -388,12 +388,12 @@ void ANT::requestFecCalibration(uint8_t type) void ANT::requestPwrCapabilities1(const uint8_t chan) { - sendMessage(ANTMessage::requestPwrCapabilities1(chan!=-1?chan:pwrChannel)); + sendMessage(ANTMessage::requestPwrCapabilities1(chan!=255?chan:pwrChannel)); } void ANT::requestPwrCapabilities2(const uint8_t chan) { - sendMessage(ANTMessage::requestPwrCapabilities2(chan!=-1?chan:pwrChannel)); + sendMessage(ANTMessage::requestPwrCapabilities2(chan!=255?chan:pwrChannel)); } void ANT::enablePwrCapabilities1(const uint8_t chan, const uint8_t capabilitiesMask, const uint8_t capabilitiesSetup) diff --git a/src/FileIO/CsvRideFile.cpp b/src/FileIO/CsvRideFile.cpp index 85a549f6d..bc95320b7 100644 --- a/src/FileIO/CsvRideFile.cpp +++ b/src/FileIO/CsvRideFile.cpp @@ -133,7 +133,7 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors, QListname = "TCORE"; - tcoreSeries->valuename << "Core" << "Skin" << "HSI" << "Quality"; - tcoreSeries->unitname << "C" << "C" << "%" << "q"; + // create the XDATA series + develSeries = new XDataSeries(); + + develSeries->name = "DEVELOPER"; + develSeries->valuename << "core_temperature" << "skin_temperature" << "heat_strain_index" << "core_data_quality"; + develSeries->unitname << "C" << "C" << "%" << "q"; // attempt to read and add the data lineno=1; @@ -1609,12 +1611,12 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors, QListsecs = values.at(0).toDouble(); - p->km = 0; - p->number[0] = values.at(1).toDouble(); - p->number[1] = values.at(2).toDouble(); - p->number[2] = values.at(3).toDouble(); - p->number[3] = values.at(4).toInt(); - tcoreSeries->datapoints.append(p); + p->km = 0; //why not add km too? + p->number[0] = values.at(1).toDouble(); //core + p->number[1] = values.at(2).toDouble(); //skin + p->number[2] = values.at(3).toDouble(); //qual + p->number[3] = values.at(4).toInt(); //hsi + develSeries->datapoints.append(p); } // onto next line @@ -1623,9 +1625,20 @@ RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors, QListdatapoints.count() > 0) rideFile->addXData("TCORE", tcoreSeries); + + // Now add the developer fields + if (develSeries->datapoints.count() > 0) { + rideFile->addXData("DEVELOPER", develSeries); + + // Since we're creating source data as if a device, + // add the one CIQ developer and field info + CIQinfo info("6957fe68-83fe-4ed6-8613-413f70624bb5", 0, 64); + info.fields << CIQfield("record","core_temperature", 139, 0, "float32", "°C", -1, -1); + info.fields << CIQfield("record","skin_temperature", -1, 10, "float32", "°C", -1, -1); + info.fields << CIQfield("record","core_data_quality", -1, 19, "sint16", "Q", -1, -1); + info.fields << CIQfield("record","heat_strain_index", -1, 95, "float32", "a.u.", -1, -1); + rideFile->addCIQ(info); // store ciq metadata for fit writer + } } diff --git a/src/FileIO/FitRideFile.cpp b/src/FileIO/FitRideFile.cpp index 124ce3aa4..08265e29e 100644 --- a/src/FileIO/FitRideFile.cpp +++ b/src/FileIO/FitRideFile.cpp @@ -20,7 +20,6 @@ #include "FitRideFile.h" #include "Settings.h" -#include "Units.h" #include "RideItem.h" #include "Specification.h" #include "DataProcessor.h" @@ -95,6 +94,45 @@ QList FITstandardfields; QStringList GenericDecodeList; +//Convert metadata typeids to fit ids +int typeToFIT(QString type) +{ + int id = FITbasetypes.indexOf(type); + switch (id){ + case 1: //sint8 + return 1; + case 2: //uint8 + return 2; + case 3: //sint16 + return 131; + case 4: //uint16 + return 132; + case 5: //sint32 + return 133; + case 6: //uint32 + return 134; + case 8: //float32 + return 136; + } + return -1; +} + +//Convert metadata typeids to fit lengths +int typeLength(QString type) +{ + int id = FITbasetypes.indexOf(type); + switch (id){ + case 1: return 1; + case 2: return 1; + case 3: return 2; + case 4: return 2; + case 5: return 4; + case 6: return 4; + case 8: return 4; + } + return 0; +} + // load the FITmetadata file, called the first time a FIT file // is parsed, so may not ever be called by the user bool loaded=false; @@ -1499,8 +1537,8 @@ struct FitFileParser case 138: // Stamina return "STAMINA"; - case 139: // CoreTemp - return "CORETEMP"; + case 139: // CoreTemp + return "CORETEMP"; case 140: // Gap return "GAP"; @@ -1541,9 +1579,6 @@ struct FitFileParser case 116: // Stress return 100.0; - // case 139: //CORETEMP //do we need to scale here? - // return 0.01; - default: return 1.0; } @@ -1561,7 +1596,8 @@ struct FitFileParser void addRecordDeveField(QString key, FitFieldDefinition deveField, bool xdata) { QString name = deveField.name.c_str(); - if (deveField.native>-1) { + //If field has a native type and doesnt have a name, generate one + if (deveField.native>-1 && name.length()==0 ) { int i = 0; RideFile::SeriesType series = getSeriesForNative(deveField.native); QString nativeName = rideFile->symbolForSeries(series); @@ -2816,7 +2852,6 @@ genericnext: } if (native_num>-1) { - switch (native_num) { case 253: // TIMESTAMP time = value + qbase_time.toSecsSinceEpoch(); @@ -3036,23 +3071,27 @@ genericnext: case 133: // Pulse Ox native_num = -1; break; - case 139: // Core Temp - tcore = value; - break; + case 139: // Core Temp + if (field.deve_idx>-1) { + tcore = deve_value; + } else { + tcore = value; + } + break; default: unknown_record_fields.insert(native_num); native_num = -1; } } - if (native_num == -1) { + if (native_num == -1 || field.deve_idx>-1) { // native, deve_native or deve to record. int idx = -1; if (field.deve_idx>-1) { QString key = QString("%1.%2").arg(field.deve_idx).arg(field.num); - FitFieldDefinition deveField = local_deve_fields[key]; + FitFieldDefinition deveField = local_deve_fields[key]; if (!record_deve_fields.contains(key)) { addRecordDeveField(key, deveField, true); @@ -3916,6 +3955,7 @@ genericnext: break; } } + fieldDef.message = fitMessageDesc(native_mesg_num,true).toStdString(); if (FIT_DEBUG && FIT_DEBUG_LEVEL>0) { // always show these regardless of debug level @@ -3935,8 +3975,8 @@ genericnext: // add or replace with new definition local_deve_fields.insert((key), fieldDef); - - if (native_mesg_num == RECORD_MSG_NUM && fieldDef.native > -1 && !record_deve_native_fields.values().contains(fieldDef.native)) { + if ((native_mesg_num == RECORD_MSG_NUM) && fieldDef.native > -1 && + !record_deve_native_fields.values().contains(fieldDef.native)) { record_deve_native_fields.insert(key, fieldDef.native); /*RideFile::SeriesType series = getSeriesForNative(fieldDef.native); @@ -4712,25 +4752,34 @@ genericnext: // CIQINFO if (local_deve_fields_app.count()>0) { - QString ciqInfo = "["; - int i=0; - foreach(FitDeveApp deve_app, local_deve_fields_app) { - ciqInfo += QString("{\n\t\"application_id\": %1,\n\t\"developer_data_index\": %2,\n\t").arg(deve_app.app_id.c_str() ).arg(deve_app.dev_data_id); - ciqInfo += QString("\"fields\": [\n\t\t"); - int j=0; - foreach(FitFieldDefinition deve_field, deve_app.fields) { - ciqInfo += QString("{\n\t\t\"dev_id\": %1,\n\t\t\"name\": \"%2\"}").arg(deve_field.dev_id).arg(deve_field.name.c_str()); - if (++j2) - // rideFile->setTag("CIQ", ciqInfo); + foreach(FitDeveApp deve_app, local_deve_fields_app) { + CIQinfo info(deve_app.app_id.c_str(), + deve_app.dev_data_id, + deve_app.app_version); + + foreach(FitFieldDefinition deve_field, deve_app.fields) { + info.fields << CIQfield(deve_field.message.c_str(), //always record atm + deve_field.name.c_str(), + deve_field.native, + deve_field.num, + FITbasetypes[deve_field.type], + deve_field.unit.c_str(), + deve_field.scale, + deve_field.offset); + } + rideFile->addCIQ(info); + } + //add ciq infos as tag + + if (!rideFile->ciqinfo().empty() ) { + + QString ciqString = CIQinfo::listToJson( rideFile->ciqinfo()); + + QString escaped = QJsonValue(ciqString).toVariant().toString(); + + rideFile->setTag("CIQ", escaped ); + } } file.close(); @@ -4776,6 +4825,9 @@ RideFile *FitFileReader::openRideFile(QFile &file, QStringList &errors, QListappend(value); } +void write_uint8(QByteArray* array, fit_value_t value) { + array->append(value); +} void write_int16(QByteArray *array, fit_value_t value, bool is_big_endian) { value = is_big_endian @@ -4787,6 +4839,16 @@ void write_int16(QByteArray *array, fit_value_t value, bool is_big_endian) { } } +void write_uint16(QByteArray *array, fit_value_t value, bool is_big_endian) { + value = is_big_endian + ? qFromBigEndian( value ) + : qFromLittleEndian( value ); + + for (int i=0; i<16; i=i+8) { + array->append(value >> i); + } +} + void write_int32(QByteArray *array, fit_value_t value, bool is_big_endian) { value = is_big_endian ? qFromBigEndian( value ) @@ -4794,6 +4856,18 @@ void write_int32(QByteArray *array, fit_value_t value, bool is_big_endian) { + for (int i=0; i<32; i=i+8) { + array->append(value >> i); + } +} + +void write_uint32(QByteArray *array, fit_value_t value, bool is_big_endian) { + value = is_big_endian + ? qFromBigEndian( value ) + : qFromLittleEndian( value ); + + + for (int i=0; i<32; i=i+8) { array->append(value >> i); } @@ -4942,29 +5016,8 @@ void write_file_creator(QByteArray *array) { } void write_session(QByteArray* array, const RideFile* ride, QHash computed) { - QByteArray* fields = new QByteArray(); - write_message_definition(array, FIELD_DESCRIPTION, 0, 7); - - write_field_definition(fields, 3, 64, 0x07); //'core_temperature' - write_field_definition(fields, 8, 16, 0x07); //'°C' - write_field_definition(fields, 14, 2, 0x84); // 20 - record - write_field_definition(fields, 1, 1, 0x02); // uint8 - local id - write_field_definition(fields, 2, 1, 0x02); // uint8 - (float32) 136 - write_field_definition(fields, 15, 1, 0x02); // unit8 - (native#) 139 - write_field_definition(fields, 0, 1, 0x02); // developer id - - array->append(fields->data(), fields->size()); - write_int8(array, 0); - write_string(array, "CIQ_device_info", 64); - write_string(array, "", 16); - write_int16(array, 18, true); - write_int8(array, 40); // Local num (increment counter?) - write_int8(array, 2); - write_int8(array, 255); // Native num - write_int8(array, 0); // developer id 0 - - write_message_definition(array, SESSION_MSG_NUM, 32, 10); // global_msg_num, local_msg_type, num_fields + write_message_definition(array, SESSION_MSG_NUM, 0, 10); // global_msg_num, local_msg_type, num_fields write_field_definition(array, 253, 4, 134); // timestamp (253) write_field_definition(array, 254, 2, 132); // message_index (254) @@ -4976,8 +5029,6 @@ void write_session(QByteArray* array, const RideFile* ride, QHashappend((const char*)ciq_id, 14); } void write_lap(QByteArray *array, const RideFile *ride) { @@ -5187,86 +5236,63 @@ void write_activity(QByteArray *array, const RideFile *ride, QHashareDataPresent()->tcore) { + const QList &ciqlist = ride->ciqinfo(); + //Do we have developer field definitions? + if (!ciqlist.empty()){ + foreach (CIQinfo ciqinfo, ciqlist) + { + int dev_idx = ciqinfo.devid; + QByteArray* fields = new QByteArray(); - // Minimal developer header - write_message_definition(array, 207, local_msg_type, 5); // global_msg_num, local_msg_type, num_fields - write_field_definition(array, 0, 16, 13); // developer id - write_field_definition(array, 1, 16, 13); // application id - write_field_definition(array, 2, 2, 132); // manufacturer_id - write_field_definition(array, 3, 1, 2); // developer index - write_field_definition(array, 4, 4, 6); // app_version + // Minimal developer header + write_message_definition(array, 207, local_msg_type, 3); // global_msg_num, local_msg_type, num_fields + write_field_definition(array, 1, 16, 13); // application id + write_field_definition(array, 3, 1, 2); // developer index + write_field_definition(array, 4, 4, 6); // app_version - uint8_t app_id[] = { 105, 87, 254, 104, 131, 254, 78, 214, 134, 19, 65, 63, 112, 98, 75, 181 }; - uint8_t dev_id[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; + QString tmpid(ciqinfo.appid); + tmpid.remove('-');//why bother adding the hyphens? + QByteArray byteArray = QByteArray::fromHex(tmpid.toUtf8()); - write_int8(array, 0); - array->append((const char*)dev_id, 16); - array->append((const char*)app_id, 16); - write_int16(array, 65535, true); // Invalid - write_int8(array, 0); - write_int32(array, 42, true); + write_int8(array, 0); + array->append((const char*)byteArray.data(), 16); + write_int8(array, dev_idx); + write_int32(array, ciqinfo.ver, true); - // Store core temerature as a developer field until included in ANT spec - write_field_definition(fields, 3, 64, 0x07); //'core_temperature' - write_field_definition(fields, 8, 16, 0x07); //'°C' - write_field_definition(fields, 14, 2, 0x84); // 20 - record - write_field_definition(fields, 1, 1, 0x02); // uint8 - local id - write_field_definition(fields, 2, 1, 0x02); // uint8 - (float32) 136 - write_field_definition(fields, 15, 1, 0x02); // unit8 - (native#) 139 - write_field_definition(fields, 0, 1, 0x02); // developer id + //Add field definitions + write_field_definition(fields, 3, 64, 0x07); // field name + write_field_definition(fields, 8, 16, 0x07); // units + write_field_definition(fields, 14, 2, 0x84); // native-msg (20) + write_field_definition(fields, 1, 1, 0x02); // field_definition id + write_field_definition(fields, 2, 1, 0x02); // fit base type + write_field_definition(fields, 15, 1, 0x02); // native field# + write_field_definition(fields, 0, 1, 0x02); // developer id + write_field_definition(fields, 6, 1, 0x02); // scale - uint + write_field_definition(fields, 7, 1, 0x01); // offset - sint - num_fields = 7; + foreach (CIQfield field, ciqinfo.fields) { + write_message_definition(array, FIELD_DESCRIPTION, local_msg_type, 9); + array->append(fields->data(), fields->size()); + write_int8(array, 0); + write_string(array, field.name.toStdString().c_str(), 64); + write_string(array, field.unit.toStdString().c_str(), 16); + write_int16(array, 20, true); // record type + write_int8(array, field.id); // Local num + write_int8(array, typeToFIT(field.type)); // type + write_int8(array, field.nativeid); // Native num + write_int8(array, dev_idx); // developer id + write_uint8(array, (field.scale < 0) ? 255 : field.scale); // Scale + write_int8(array, (field.offset < 0) ? 127 : field.offset); // Offset - write_message_definition(array, FIELD_DESCRIPTION, local_msg_type, num_fields); - array->append(fields->data(), fields->size()); - write_int8(array, 0); - write_string(array, "core_temperature", 64); - write_string(array, "°C", 16); - write_int16(array, 20, true); - write_int8(array, /*8*/ 0); // Local num (increment counter?) - write_int8(array, 136); - write_int8(array, 139); // Native num - write_int8(array, 0); // developer id 0 - - write_message_definition(array, FIELD_DESCRIPTION, local_msg_type, num_fields); - array->append(fields->data(), fields->size()); - write_int8(array, 0); - write_string(array, "skin_temperature", 64); - write_string(array, "°C", 16); - write_int16(array, 20, true); // record - write_int8(array, 10); // Local num (increment counter?) - write_int8(array, 136); - write_int8(array, 255); // Native num - write_int8(array, 0); // developer id 0 - - write_message_definition(array, FIELD_DESCRIPTION, local_msg_type, num_fields); - array->append(fields->data(), fields->size()); - write_int8(array, 0); - write_string(array, "core_data_quality", 64); - write_string(array, "Q", 16); - write_int16(array, 20, true); // record - write_int8(array, 19); // Local num (increment counter?) - write_int8(array, 131); - write_int8(array, 255); // Native num - write_int8(array, 0); // developer id 0 - - write_message_definition(array, FIELD_DESCRIPTION, local_msg_type, num_fields); - array->append(fields->data(), fields->size()); - write_int8(array, 0); - write_string(array, "core_reserved", 64); - write_string(array, "kcal", 16); - write_int16(array, 20, true); // record - write_int8(array, 20); // Local num (increment counter?) - write_int8(array, 131); - write_int8(array, 255); // Native num - write_int8(array, 0); // developer id 0 + num_fields++; + } + } } + return num_fields; } void write_record_definition(QByteArray *array, const RideFile *ride, QMap *local_msg_type_for_record_type, bool withAlt, bool withWatts, bool withHr, bool withCad, int type) { @@ -5318,16 +5344,28 @@ void write_record_definition(QByteArray *array, const RideFile *ride, QMapvalues().count()+1; - bool withDev = false; // Do we need our developer fields adding? - if (ride->areDataPresent()->tcore) { + bool withDev = false; + const QList &ciqlist = ride->ciqinfo(); + + //Do we have developer field definitions? + if (!ciqlist.empty()){ withDev = true; - write_dev_fields(array, ride, 0); // add custom field definitions + int numfields = write_dev_fields(array, ride, 0); // add custom field definitions - write_int8(fields, 1); // one dev field - write_field_definition(fields, /*8*/ 0, 4, 0); // tcore + write_int8(fields, numfields); + + foreach (CIQinfo ciqinfo, ciqlist) + { + int dev_idx = ciqinfo.devid; + + foreach (CIQfield field, ciqinfo.fields) + { + write_field_definition(fields, field.id, typeLength(field.type), dev_idx); + } + } } - + // Add developer field flag to header write_message_definition(array, RECORD_MSG_NUM, local_msg_type | (withDev ? 32 : 0), num_fields); // global_msg_num, local_msg_type, num_fields @@ -5339,6 +5377,7 @@ void write_record_definition(QByteArray *array, const RideFile *ride, QMap *local_msg_type_for_record_type = new QMap(); + int xdata_cur=0; //cursor into core xdata // Record ------ foreach (const RideFilePoint *point, ride->dataPoints()) { int type = 0; @@ -5397,13 +5436,51 @@ void write_record(QByteArray *array, const RideFile *ride, bool withAlt, bool wi // write right power contribution write_int8(ridePoint, 0x80 + (100-point->lrbalance)); } - if (ride->areDataPresent()->tcore) { - write_float32(ridePoint, point->tcore, true); - } + const QList &ciqlist = ride->ciqinfo(); + //Do we have developer field definitions? + if (!ciqlist.empty()){ + foreach (CIQinfo ciqinfo, ciqlist) + { + int dev_idx = ciqinfo.devid; + static_cast(dev_idx); + + foreach (CIQfield ciqfield, ciqinfo.fields) + { + double val = ride->xdataValue(point, xdata_cur, "DEVELOPER", ciqfield.name, RideFile::REPEAT); + int id = FITbasetypes.indexOf(ciqfield.type); + switch (id){ //limited set supported + case 1: + write_int8(ridePoint, val); + break; + case 2: + write_uint8(ridePoint, val); + break; + case 3: + write_int16(ridePoint, val, true); + break; + case 4: + write_uint16(ridePoint, val, true); + break; + case 5: + write_int32(ridePoint, val, true); + break; + case 6: + write_uint32(ridePoint, val, true); + break; + case 8: + write_float32(ridePoint, val, true); + break; + default: + fprintf(stderr,"ERROR TYPE % CURRENTLY UNSUPPORTED\n",ciqfield.type); fflush(stderr); + } + } + } + } array->append(ridePoint->data(), ridePoint->size()); } + delete local_msg_type_for_record_type; } QByteArray @@ -5473,6 +5550,3 @@ FitFileReader::writeRideFile(Context *context, const RideFile *ride, QFile &file } - - - diff --git a/src/FileIO/RideFile.cpp b/src/FileIO/RideFile.cpp index 22cde47de..156df509c 100644 --- a/src/FileIO/RideFile.cpp +++ b/src/FileIO/RideFile.cpp @@ -30,6 +30,10 @@ #include "Units.h" #include "SplineLookup.h" +#include +#include +#include + #include #include // for std::lower_bound #include @@ -1014,12 +1018,28 @@ RideFile *RideFileFactory::openRideFile(Context *context, QFile &file, p->km = p->km - kmOffset; p->secs = p->secs - timeOffset; } - } - // drag back intervals - foreach(RideFileInterval *i, result->intervals()) { - i->start -= timeOffset; - i->stop -= timeOffset; + // drag back intervals + foreach(RideFileInterval *i, result->intervals()) { + i->start -= timeOffset; + i->stop -= timeOffset; + } + + // drag back DEVELOPER xdata + QMapIterator it(result->xdata()); + while(it.hasNext()) { + it.next(); + XDataSeries *s = it.value(); + if (s->name == "DEVELOPER") + { + foreach (XDataPoint *p, s->datapoints) { + if (p->secs>0) + p->secs = p->secs - timeOffset; + if (p->km>0) + p->km = p->km -kmOffset; + } + } + } } // calculate derived data series -- after data fixers applied above @@ -1041,6 +1061,23 @@ RideFile *RideFileFactory::openRideFile(Context *context, QFile &file, // what data is present - after processor in case 'derived' or adjusted result->updateDataTag(); + //If we have a CIQ tag, populate the CIQinfo + QString ciq = result->getTag("CIQ",""); + if (!ciq.isEmpty()) + { + QJsonDocument doc = QJsonDocument::fromJson(ciq.toUtf8()); + + if (!doc.isNull() && (doc.isObject() || doc.isArray())) + { + ciq = doc.toJson(QJsonDocument::Compact); + + QList infos = CIQinfo::listFromJson(ciq); + foreach(CIQinfo info, infos) + { + result->addCIQ(info); + } + } + } //foreach(RideFile::seriestype x, result->arePresent()) qDebug()<<"present="<datapoints.count() > 0) { + if (devseries->valuename.contains("core_temperature")) + { + setDataPresent(RideFile::tcore, true); + coreidx = 0; + } + } + foreach(RideFilePoint *p, dataPoints_) { // Delta @@ -2722,14 +2776,6 @@ RideFile::recalculateDerivedSeries(bool force) } } - // Pull tcore from XData if it exists - series = xdata("TCORE"); - if (series && series->datapoints.count() > 0) { - setDataPresent(RideFile::tcore, true); - int idx=0; - p->tcore = xdataValue(p, idx, "TCORE","Core", RideFile::REPEAT); - } - // split out O2Hb and HHb when we have SmO2 and tHb // O2Hb is oxygenated haemoglobin and HHb is deoxygenated haemoglobin if (dataPresent.smo2 && dataPresent.thb) { @@ -2771,6 +2817,12 @@ RideFile::recalculateDerivedSeries(bool force) p->clength = 0.0f; } + // Since TCORE isn't stored in json, retrieve it from XDATA + // Otherwise, derive it later + // + if (coreidx>=0) + p->tcore = xdataValue(p, coreidx, "DEVELOPER","core_temperature", RideFile::REPEAT); + // last point lastP = p; } @@ -2842,7 +2894,7 @@ RideFile::recalculateDerivedSeries(bool force) // in between // we need HR data for this - // don't derive if we already have tcore data + // but don't derive if we already have tcore data if (dataPresent.hr && !dataPresent.tcore) { // resample the data into 60s samples @@ -3577,3 +3629,105 @@ XDataSeries::timeIndex(double secs) const return datapoints.size()-1; return i - datapoints.begin(); } + +const char* CIQ_KEY = "application_id"; +const char* CIQ_ID = "developer_data_index"; +const char* VER_KEY = "verison"; +const char* FIELDS_KEY = "fields"; + +const char* CIQ_FIELD_MESSAGE="message"; +const char* CIQ_FIELD_NAME="name"; +const char* CIQ_FIELD_NATIVE="native_id"; +const char* CIQ_FIELD_ID="dev_id"; +const char* CIQ_FIELD_TYPE="type"; +const char* CIQ_FIELD_UNIT="unit"; +const char* CIQ_FIELD_SCALE="scale"; +const char* CIQ_FIELD_OFFSET="offset"; + +QString CIQinfo::listToJson(const QList& ciqList) +{ + QJsonArray ciqArray; + foreach (const CIQinfo& ciq, ciqList) + { + QJsonObject ciqObj; + + ciqObj[QString(CIQ_KEY)] = ciq.appid; + ciqObj[QString(CIQ_ID)] = ciq.devid; + ciqObj[QString(VER_KEY)] = ciq.ver; + + QJsonArray fieldsArray; + foreach (const CIQfield& field, ciq.fields) + { + QJsonObject fieldObj; + fieldObj[CIQ_FIELD_MESSAGE] = field.message; + fieldObj[CIQ_FIELD_NAME] = field.name; + fieldObj[CIQ_FIELD_NATIVE] = field.nativeid; + fieldObj[CIQ_FIELD_ID] = field.id; + fieldObj[CIQ_FIELD_TYPE] = field.type; + fieldObj[CIQ_FIELD_UNIT] = field.unit; + fieldObj[CIQ_FIELD_SCALE] = field.scale; + fieldObj[CIQ_FIELD_OFFSET] = field.offset; + + fieldsArray.append(fieldObj); + } + + ciqObj[QString(FIELDS_KEY)] = fieldsArray; + + ciqArray.append(ciqObj); + } + QJsonDocument jsonDoc(ciqArray); + + //Do we want it nicely formatted or compact? + QString jsonString = jsonDoc.toJson(QJsonDocument::Indented); + jsonString.replace(" ", "\t"); + + //QString jsonString = jsonDoc.toJson(QJsonDocument::Compact); + return jsonString; +} + +QList CIQinfo::listFromJson(const QString& src) +{ + QList ciqList; + + QJsonDocument doc = QJsonDocument::fromJson(src.toUtf8()); + + //Allow for either an array or single item + if (doc.isArray()) + { + QJsonArray ciqArray = doc.array(); + foreach (const QJsonValue& ciqValue, ciqArray) + { + QJsonObject ciqObj = ciqValue.toObject(); + ciqList.append(CIQinfo(ciqObj)); + } + } + else if (doc.isObject()) + { + ciqList.append(CIQinfo(doc.object())); + } + + return ciqList; +} + +CIQinfo::CIQinfo(const QJsonObject& obj) +{ + appid = obj[QString(CIQ_KEY)].toString(); + ver = obj[QString(VER_KEY)].toInt(); + devid = obj[QString(CIQ_ID)].toInt(); + + QJsonArray jsonfields = obj[QString(FIELDS_KEY)].toArray(); + foreach (const QJsonValue& field, jsonfields) + { + QJsonObject fieldObj = field.toObject(); + CIQfield ciqfield(fieldObj[CIQ_FIELD_MESSAGE].toString(), + fieldObj[CIQ_FIELD_NAME].toString(), + fieldObj[CIQ_FIELD_NATIVE].toInt(), + fieldObj[CIQ_FIELD_ID].toInt(), + fieldObj[CIQ_FIELD_TYPE].toString(), + fieldObj[CIQ_FIELD_UNIT].toString(), + fieldObj[CIQ_FIELD_SCALE].toInt(), + fieldObj[CIQ_FIELD_OFFSET].toInt()); + + fields.append(ciqfield); + } +} diff --git a/src/FileIO/RideFile.h b/src/FileIO/RideFile.h index 3b3d7ef66..87e9711e9 100644 --- a/src/FileIO/RideFile.h +++ b/src/FileIO/RideFile.h @@ -89,6 +89,36 @@ struct RideFileDataPresent }; +// Mappings for QIC/ANT fields info +struct CIQfield { + CIQfield(QString message, QString name, int nativeid, int id, QString type, QString unit, int scale, int offset) + : message(message), name(name), nativeid(nativeid), id(id), scale(scale), offset(offset), type(type), unit(unit) {} + + QString message; + QString name; + int nativeid; + int id; + int scale; + int offset; + QString type; + QString unit; +// int size; +}; + +struct CIQinfo +{ + CIQinfo(QString appid, int id, int ver) : appid(appid), devid(id), ver(ver){} + CIQinfo(const QJsonObject& obj); + + static QString listToJson(const QList& ciqList); + static QList listFromJson(const QString& src); + + QString appid; + int devid; + int ver; + QList fields; +}; + class RideFileInterval { Q_DECLARE_TR_FUNCTIONS(RideFileInterval); @@ -379,7 +409,9 @@ class RideFile : public QObject // QObject to emit signals XDataSeries *xdata(QString name) const { return xdata_.value(name, NULL); } void addXData(QString name, XDataSeries *series); QMap &xdata() { return xdata_; } - double xdataValue(RideFilePoint *p, int &idx, QString xdata, QString series, RideFile::XDataJoin); + double xdataValue(const RideFilePoint *p, int &idx, QString xdata, QString series, RideFile::XDataJoin) const; + void addCIQ(CIQinfo &ciqinfo); + const QList& ciqinfo() const { return ciqinfo_; } // METRIC OVERRIDES QMap > metricOverrides; @@ -423,6 +455,7 @@ class RideFile : public QObject // QObject to emit signals // xdata series QMap xdata_; + QList ciqinfo_; //ciq metadata void clearIntervals(); void fillInIntervals(); diff --git a/src/Metrics/RideMetadata.cpp b/src/Metrics/RideMetadata.cpp index a705804de..d3d0faf8b 100644 --- a/src/Metrics/RideMetadata.cpp +++ b/src/Metrics/RideMetadata.cpp @@ -272,7 +272,12 @@ RideMetadata::setExtraTab() } else { // set Text Field to 'Read Only' to still enable scrolling,... GTextEdit* textEdit = dynamic_cast (field->widget); - if (textEdit) textEdit->setReadOnly(true); + if (textEdit){ + textEdit->setReadOnly(true); + //set tabs to 4 for edit box and dont wrap + textEdit->setLineWrapMode(QTextEdit::NoWrap); + textEdit->setTabStopDistance(4 * textEdit->fontMetrics().horizontalAdvance(' ')); + } else { QLineEdit* lineEdit = dynamic_cast (field->widget); if (lineEdit) lineEdit->setReadOnly(true); diff --git a/test/charts/CoreTemp.gchart b/test/charts/CoreTemp.gchart index 3faafab25..322815499 100644 --- a/test/charts/CoreTemp.gchart +++ b/test/charts/CoreTemp.gchart @@ -10,7 +10,7 @@ "heightFactor":"2", "style":"0", "resizable":"0", - "settings":"{ \"title\": \" \",\n\"description\": \" \",\n\"type\": 1,\n\"animate\": false,\n\"intervalrefresh\": false,\n\"legendpos\": 2,\n\"stack\": false,\n\"orientation\": 1,\n\"bgcolor\": \"#010169\", \n\"scale\": 1,\n\"SERIES\": [\n{ \"name\": \"Core \", \"group\": \" \", \"xname\": \"Elapsed Time \", \"yname\": \"C \", \"program\": \"{:sl:n init {:sl:n xx<-c();:sl:n yy<-c();:sl:n count<-0;:sl:n }:sl:n:sl:n finalise {:sl:n # we just fetch satramples at end:sl:n xx <- xdata(:qu:TCORE:qu:, secs);:sl:n yy <- xdata(:qu:TCORE:qu:, :qu:Core:qu:);:sl:n annotate(label, :qu:MAX:qu:, round(max(yy)));:sl:n }:sl:n:sl:n x { xx; }:sl:n y { yy; }:sl:n} \", \"line\": 1, \"symbol\": 0, \"size\": 3, \"color\": \"#01010f\", \"opacity\": 100, \"legend\": true, \"opengl\": true, \"datalabels\": false, \"aggregate\": 0, \"fill\": false},\n{ \"name\": \"Skin \", \"group\": \" \", \"xname\": \"Elapsed Time \", \"yname\": \"C \", \"program\": \"{:sl:n init {:sl:n xx<-c();:sl:n yy<-c();:sl:n count<-0;:sl:n }:sl:n:sl:n finalise {:sl:n # we just fetch satramples at end:sl:n xx <- xdata(:qu:TCORE:qu:, secs);:sl:n yy <- xdata(:qu:TCORE:qu:, :qu:Skin:qu:);:sl:n annotate(label, :qu:MAX:qu:, round(max(yy)));:sl:n }:sl:n:sl:n x { xx; }:sl:n y { yy; }:sl:n} \", \"line\": 1, \"symbol\": 0, \"size\": 2, \"color\": \"#010152\", \"opacity\": 100, \"legend\": true, \"opengl\": true, \"datalabels\": false, \"aggregate\": 0, \"fill\": false},\n{ \"name\": \"HR \", \"group\": \" \", \"xname\": \"Elapsed Time \", \"yname\": \"bpm \", \"program\": \"{:sl:n init {:sl:n xx<-c();:sl:n yy<-c();:sl:n count<-0;:sl:n }:sl:n:sl:n relevant {:sl:n Data contains :qu:P:qu:;:sl:n }:sl:n:sl:n finalise {:sl:n # we just fetch samples at end:sl:n xx <- samples(SECS);:sl:n yy <- samples(HEARTRATE);:sl:n:sl:tannotate(label, :qu:AVG:qu:, round(mean(yy)));:sl:n }:sl:n:sl:n x { xx; }:sl:n y { yy; }:sl:n}:sl:n \", \"line\": 1, \"symbol\": 0, \"size\": 3, \"color\": \"#ff0000\", \"opacity\": 100, \"legend\": true, \"opengl\": true, \"datalabels\": false, \"aggregate\": -1, \"fill\": false},\n{ \"name\": \"Power \", \"group\": \" \", \"xname\": \"Elapsed Time \", \"yname\": \"watts \", \"program\": \"{:sl:n init {:sl:n xx<-c();:sl:n yy<-c();:sl:n count<-0;:sl:n }:sl:n:sl:n relevant {:sl:n Data contains :qu:P:qu:;:sl:n }:sl:n:sl:n finalise {:sl:n # we just fetch samples at end:sl:n xx <- samples(SECS);:sl:n yy <- samples(POWER);:sl:n:sl:tannotate(label, :qu:AVG:qu:, round(mean(yy)));:sl:n }:sl:n:sl:n x { xx; }:sl:n y { yy; }:sl:n}:sl:n \", \"line\": 1, \"symbol\": 0, \"size\": 2, \"color\": \"#53297c\", \"opacity\": 100, \"legend\": true, \"opengl\": true, \"datalabels\": false, \"aggregate\": -1, \"fill\": true} ]\n,\n\"AXES\": [\n{ \"name\": \"Elapsed Time \", \"type\": 2, \"orientation\": 1, \"align\": 1, \"minx\": 0, \"maxx\": 0, \"miny\": 0, \"maxy\": 0, \"smooth\": 5, \"groupby\": 0, \"visible\": true, \"fixed\": false, \"log\": false, \"minorgrid\": false, \"majorgrid\": true, \"labelcolor\": \"#55aaff\", \"axiscolor\": \"#55aaff\"},\n{ \"name\": \"C \", \"type\": 0, \"orientation\": 2, \"align\": 1, \"minx\": 0, \"maxx\": 0, \"miny\": 0, \"maxy\": 0, \"smooth\": 0, \"groupby\": 0, \"visible\": true, \"fixed\": false, \"log\": false, \"minorgrid\": false, \"majorgrid\": true, \"labelcolor\": \"#01010f\", \"axiscolor\": \"#01010f\"},\n{ \"name\": \"bpm \", \"type\": 0, \"orientation\": 2, \"align\": 1, \"minx\": 0, \"maxx\": 0, \"miny\": 80, \"maxy\": 185, \"smooth\": 0, \"groupby\": 0, \"visible\": true, \"fixed\": true, \"log\": false, \"minorgrid\": false, \"majorgrid\": true, \"labelcolor\": \"#ff0000\", \"axiscolor\": \"#ff0000\"},\n{ \"name\": \"watts \", \"type\": 0, \"orientation\": 2, \"align\": 1, \"minx\": 0, \"maxx\": 0, \"miny\": 0, \"maxy\": 1000, \"smooth\": 0, \"groupby\": 0, \"visible\": true, \"fixed\": false, \"log\": false, \"minorgrid\": false, \"majorgrid\": true, \"labelcolor\": \"#53297c\", \"axiscolor\": \"#53297c\"} ]\n} ", + "settings":"{ \"title\": \" \",\n\"description\": \" \",\n\"type\": 1,\n\"animate\": false,\n\"intervalrefresh\": false,\n\"legendpos\": 2,\n\"stack\": false,\n\"orientation\": 1,\n\"bgcolor\": \"#010169\", \n\"scale\": 1,\n\"SERIES\": [\n{ \"name\": \"Core \", \"group\": \" \", \"xname\": \"Elapsed Time \", \"yname\": \"C \", \"program\": \"{:sl:n init {:sl:n xx<-c();:sl:n yy<-c();:sl:n count<-0;:sl:n }:sl:n:sl:n finalise {:sl:n # we just fetch satramples at end:sl:n xx <- xdata(:qu:DEVELOPER:qu:, secs);:sl:n yy <- xdata(:qu:DEVELOPER:qu:, :qu:core_temperature:qu:);:sl:n annotate(label, :qu:MAX:qu:, round(max(yy)));:sl:n }:sl:n:sl:n x { xx; }:sl:n y { yy; }:sl:n} \", \"line\": 1, \"symbol\": 0, \"size\": 3, \"color\": \"#01010f\", \"opacity\": 100, \"legend\": true, \"opengl\": true, \"datalabels\": false, \"aggregate\": 0, \"fill\": false},\n{ \"name\": \"Skin \", \"group\": \" \", \"xname\": \"Elapsed Time \", \"yname\": \"C \", \"program\": \"{:sl:n init {:sl:n xx<-c();:sl:n yy<-c();:sl:n count<-0;:sl:n }:sl:n:sl:n finalise {:sl:n # we just fetch satramples at end:sl:n xx <- xdata(:qu:DEVELOPER:qu:, secs);:sl:n yy <- xdata(:qu:DEVELOPER:qu:, :qu:skin_temperature:qu:);:sl:n annotate(label, :qu:MAX:qu:, round(max(yy)));:sl:n }:sl:n:sl:n x { xx; }:sl:n y { yy; }:sl:n} \", \"line\": 1, \"symbol\": 0, \"size\": 2, \"color\": \"#010152\", \"opacity\": 100, \"legend\": true, \"opengl\": true, \"datalabels\": false, \"aggregate\": 0, \"fill\": false},\n{ \"name\": \"HR \", \"group\": \" \", \"xname\": \"Elapsed Time \", \"yname\": \"bpm \", \"program\": \"{:sl:n init {:sl:n xx<-c();:sl:n yy<-c();:sl:n count<-0;:sl:n }:sl:n:sl:n finalise {:sl:n # we just fetch samples at end:sl:n xx <- samples(SECS);:sl:n yy <- samples(HEARTRATE);:sl:n:sl:tannotate(label, :qu:AVG:qu:, round(mean(yy)));:sl:n }:sl:n:sl:n x { xx; }:sl:n y { yy; }:sl:n}:sl:n \", \"line\": 1, \"symbol\": 0, \"size\": 3, \"color\": \"#ff0000\", \"opacity\": 100, \"legend\": true, \"opengl\": true, \"datalabels\": false, \"aggregate\": -1, \"fill\": false},\n{ \"name\": \"Power \", \"group\": \" \", \"xname\": \"Elapsed Time \", \"yname\": \"watts \", \"program\": \"{:sl:n init {:sl:n xx<-c();:sl:n yy<-c();:sl:n count<-0;:sl:n }:sl:n:sl:n relevant {:sl:n Data contains :qu:P:qu:;:sl:n }:sl:n:sl:n finalise {:sl:n # we just fetch samples at end:sl:n xx <- samples(SECS);:sl:n yy <- samples(POWER);:sl:n:sl:tannotate(label, :qu:AVG:qu:, round(mean(yy)));:sl:n }:sl:n:sl:n x { xx; }:sl:n y { yy; }:sl:n}:sl:n \", \"line\": 1, \"symbol\": 0, \"size\": 2, \"color\": \"#53297c\", \"opacity\": 100, \"legend\": true, \"opengl\": true, \"datalabels\": false, \"aggregate\": -1, \"fill\": true} ]\n,\n\"AXES\": [\n{ \"name\": \"Elapsed Time \", \"type\": 2, \"orientation\": 1, \"align\": 1, \"minx\": 0, \"maxx\": 0, \"miny\": 0, \"maxy\": 0, \"smooth\": 5, \"groupby\": 0, \"visible\": true, \"fixed\": false, \"log\": false, \"minorgrid\": false, \"majorgrid\": true, \"labelcolor\": \"#55aaff\", \"axiscolor\": \"#55aaff\"},\n{ \"name\": \"C \", \"type\": 0, \"orientation\": 2, \"align\": 1, \"minx\": 0, \"maxx\": 0, \"miny\": 0, \"maxy\": 0, \"smooth\": 0, \"groupby\": 0, \"visible\": true, \"fixed\": false, \"log\": false, \"minorgrid\": false, \"majorgrid\": true, \"labelcolor\": \"#01010f\", \"axiscolor\": \"#01010f\"},\n{ \"name\": \"bpm \", \"type\": 0, \"orientation\": 2, \"align\": 1, \"minx\": 0, \"maxx\": 0, \"miny\": 80, \"maxy\": 185, \"smooth\": 0, \"groupby\": 0, \"visible\": true, \"fixed\": true, \"log\": false, \"minorgrid\": false, \"majorgrid\": true, \"labelcolor\": \"#ff0000\", \"axiscolor\": \"#ff0000\"},\n{ \"name\": \"watts \", \"type\": 0, \"orientation\": 2, \"align\": 1, \"minx\": 0, \"maxx\": 0, \"miny\": 0, \"maxy\": 1000, \"smooth\": 0, \"groupby\": 0, \"visible\": true, \"fixed\": false, \"log\": false, \"minorgrid\": false, \"majorgrid\": true, \"labelcolor\": \"#53297c\", \"axiscolor\": \"#53297c\"} ]\n} ", "__LAST__":"1" } } diff --git a/test/coretemp/FR55-CoreTemp-Walk.fit b/test/coretemp/FR55-CoreTemp-Walk.fit new file mode 100644 index 000000000..fb100ecaa Binary files /dev/null and b/test/coretemp/FR55-CoreTemp-Walk.fit differ diff --git a/test/coretemp/FR55-loaded-and-saved-via-GC.fit b/test/coretemp/FR55-loaded-and-saved-via-GC.fit new file mode 100644 index 000000000..e4850be6d Binary files /dev/null and b/test/coretemp/FR55-loaded-and-saved-via-GC.fit differ diff --git a/test/roundtrip/3386684871-saved.fit b/test/roundtrip/3386684871-saved.fit new file mode 100644 index 000000000..9d4dedaf9 Binary files /dev/null and b/test/roundtrip/3386684871-saved.fit differ diff --git a/test/roundtrip/3386684871.fit b/test/roundtrip/3386684871.fit new file mode 100644 index 000000000..5804a77cd Binary files /dev/null and b/test/roundtrip/3386684871.fit differ diff --git a/test/roundtrip/Fr955v19.28andStryd-saved.fit b/test/roundtrip/Fr955v19.28andStryd-saved.fit new file mode 100644 index 000000000..d3c95cd82 Binary files /dev/null and b/test/roundtrip/Fr955v19.28andStryd-saved.fit differ diff --git a/test/roundtrip/Fr955v19.28andStryd.fit b/test/roundtrip/Fr955v19.28andStryd.fit new file mode 100644 index 000000000..759bf5fe5 Binary files /dev/null and b/test/roundtrip/Fr955v19.28andStryd.fit differ