Files
GoldenCheetah/src/FileIO/BinRideFile.cpp
Mark Liversedge 649d3ee014 Fix Interval Color bug
.. editing a user interval color never worked properly.
   the color wasn't changed or remembered at all.

.. lots of independent bugs conspired to cause this but
   mostly it was because the interval color was not
   recorded in the ride file.

NOTE: files created with this release will not be
      backwards compatible with earlier releases.
2018-07-29 22:01:49 +01:00

798 lines
33 KiB
C++

/*
* Copyright (c) 2010 Damien Grauser (Damien.Grauser@pev-geneve.ch)
*
* 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
*/
#include "BinRideFile.h"
#include <QSharedPointer>
#include <QMap>
#include <QSet>
#include <stdio.h>
#include <stdint.h>
#include <time.h>
#define RECORD_TYPE__META 0
#define RECORD_TYPE__RIDE_DATA 1
#define RECORD_TYPE__RAW_DATA 2
#define RECORD_TYPE__SPARSE_DATA 3
#define RECORD_TYPE__INTERVAL_DATA 4
#define RECORD_TYPE__DATA_ERROR 5
#define RECORD_TYPE__HISTORY 6
#define FORMAT_ID__RIDE_START 0
#define FORMAT_ID__RIDE_SAMPLE_RATE 1
#define FORMAT_ID__FIRMWARE_VERSION 2
#define FORMAT_ID__LAST_UPDATE 3
#define FORMAT_ID__ODOMETER 4
#define FORMAT_ID__PRIMARY_POWER_ID 5
#define FORMAT_ID__SECONDARY_POWER_ID 6
#define FORMAT_ID__CHEST_STRAP_ID 7
#define FORMAT_ID__CADENCE_ID 8
#define FORMAT_ID__SPEED_ID 9
#define FORMAT_ID__RESISTANCE_UNITID 10
#define FORMAT_ID__WORKOUT_ID 11
#define FORMAT_ID__USER_WEIGHT 12
#define FORMAT_ID__USER_CATEGORY 13
#define FORMAT_ID__USER_HR_ZONE_1 14
#define FORMAT_ID__USER_HR_ZONE_2 15
#define FORMAT_ID__USER_HR_ZONE_3 16
#define FORMAT_ID__USER_HR_ZONE_4 17
#define FORMAT_ID__USER_POWER_ZONE_1 18
#define FORMAT_ID__USER_POWER_ZONE_2 19
#define FORMAT_ID__USER_POWER_ZONE_3 20
#define FORMAT_ID__USER_POWER_ZONE_4 21
#define FORMAT_ID__USER_POWER_ZONE_5 22
#define FORMAT_ID__WHEEL_CIRC 23
#define FORMAT_ID__RIDE_DISTANCE 24
#define FORMAT_ID__RIDE_TIME 25
#define FORMAT_ID__POWER 26
#define FORMAT_ID__TORQUE 27
#define FORMAT_ID__SPEED 28
#define FORMAT_ID__CADENCE 29
#define FORMAT_ID__HEART_RATE 30
#define FORMAT_ID__GRADE 31
#define FORMAT_ID__ALTITUDE_OLD 32
#define FORMAT_ID__RAW_DATA 33
#define FORMAT_ID__TEMPERATURE 34
#define FORMAT_ID__INTERVAL_NUM 35
#define FORMAT_ID__DROPOUT_FLAGS 36
#define FORMAT_ID__RAW_DATA_FORMAT 37
#define FORMAT_ID__RAW_BARO_SENSOR 38
#define FORMAT_ID__ALTITUDE 39
#define FORMAT_ID__THRESHOLD_POWER 40
#define FORMAT_ID__UNKNOW_41 41
#define FORMAT_ID__UNKNOW_42 42
#define FORMAT_ID__UNKNOW_43 43
#define FORMAT_ID__UNKNOW_44 44
#define FORMAT_ID__UNKNOW_45 45
#define FORMAT_ID__POWER_PEDAL_BALANCE 46
#define FORMAT_ID__UNKNOW_47 47
static int binFileReaderRegistered =
RideFileFactory::instance().registerReader(
"bin", "Joule Bin File", new BinFileReader());
struct BinField {
int num;
int id;
int size; // in bytes
};
struct BinDefinition {
int format_identifier;
std::vector<BinField> fields;
};
struct BinFileReaderState
{
static QMap<int,QString> global_record_types;
static QMap<int,QString> global_format_identifiers;
QMap<int, BinDefinition> local_format_identifiers;
QSet<int> unknown_record_types, unknown_format_identifiers;
QSet<int> unused_record_types;
QSet<int> unexpected_record_types;
QMap<int, QSet<int> > unknown_format_identifiers_for_record_types;
QMap<int, QSet<int> > unused_format_identifiers_for_record_types;
QMap<int, QSet<int> > unexpected_format_identifiers_for_record_types;
QFile &file;
QStringList &errors;
RideFile *rideFile;
time_t start_time;
int interval;
double last_interval_secs;
bool stopped;
BinFileReaderState(QFile &file, QStringList &errors) :
file(file), errors(errors), rideFile(NULL), start_time(0),
interval(0), last_interval_secs(0.0), stopped(true)
{
if (global_record_types.isEmpty()) {
global_record_types.insert(RECORD_TYPE__META, "Meta Data");
global_record_types.insert(RECORD_TYPE__RIDE_DATA, "1 sec detail ride data");
global_record_types.insert(RECORD_TYPE__RAW_DATA, "Raw data packet");
global_record_types.insert(RECORD_TYPE__SPARSE_DATA, "Sparse ride data");
global_record_types.insert(RECORD_TYPE__INTERVAL_DATA, "Interval");
global_record_types.insert(RECORD_TYPE__DATA_ERROR, "Data error");
global_record_types.insert(RECORD_TYPE__HISTORY, "History summary record");
}
if (global_format_identifiers.isEmpty()) {
global_format_identifiers.insert(FORMAT_ID__RIDE_START, "Ride start");
global_format_identifiers.insert(FORMAT_ID__RIDE_SAMPLE_RATE, "Ride sample rate");
global_format_identifiers.insert(FORMAT_ID__FIRMWARE_VERSION, "Firmware Version");
global_format_identifiers.insert(FORMAT_ID__LAST_UPDATE, "Last update");
global_format_identifiers.insert(FORMAT_ID__ODOMETER, "Odometer");
global_format_identifiers.insert(FORMAT_ID__PRIMARY_POWER_ID, "Primary Power Radio ID");
global_format_identifiers.insert(FORMAT_ID__SECONDARY_POWER_ID, "Secondary Power Radio ID");
global_format_identifiers.insert(FORMAT_ID__CHEST_STRAP_ID, "Chest strap Radio ID");
global_format_identifiers.insert(FORMAT_ID__CADENCE_ID, "Cadense sensor Radio ID");
global_format_identifiers.insert(FORMAT_ID__SPEED_ID, "Speed sensor Radio ID");
global_format_identifiers.insert(FORMAT_ID__RESISTANCE_UNITID, "Resistance Unit Radio ID");
global_format_identifiers.insert(FORMAT_ID__WORKOUT_ID, "Workout ID");
global_format_identifiers.insert(FORMAT_ID__USER_WEIGHT, "User weight");
global_format_identifiers.insert(FORMAT_ID__USER_CATEGORY, "User training category");
global_format_identifiers.insert(FORMAT_ID__USER_HR_ZONE_1, "User HR Zone 1");
global_format_identifiers.insert(FORMAT_ID__USER_HR_ZONE_2, "User HR Zone 2");
global_format_identifiers.insert(FORMAT_ID__USER_HR_ZONE_3, "User HR Zone 3");
global_format_identifiers.insert(FORMAT_ID__USER_HR_ZONE_4, "User HR Zone 4");
global_format_identifiers.insert(FORMAT_ID__USER_POWER_ZONE_1, "User Power Zone 1");
global_format_identifiers.insert(FORMAT_ID__USER_POWER_ZONE_2, "User Power Zone 2");
global_format_identifiers.insert(FORMAT_ID__USER_POWER_ZONE_3, "User Power Zone 3");
global_format_identifiers.insert(FORMAT_ID__USER_POWER_ZONE_4, "User Power Zone 4");
global_format_identifiers.insert(FORMAT_ID__USER_POWER_ZONE_5, "User Power Zone 5");
global_format_identifiers.insert(FORMAT_ID__WHEEL_CIRC, "Wheel circumference");
global_format_identifiers.insert(FORMAT_ID__RIDE_DISTANCE, "Ride distance");
global_format_identifiers.insert(FORMAT_ID__RIDE_TIME, "Ride time");
global_format_identifiers.insert(FORMAT_ID__POWER, "Power");
global_format_identifiers.insert(FORMAT_ID__TORQUE, "Torque");
global_format_identifiers.insert(FORMAT_ID__SPEED, "Speed");
global_format_identifiers.insert(FORMAT_ID__CADENCE, "Cadence");
global_format_identifiers.insert(FORMAT_ID__HEART_RATE, "Heart rate");
global_format_identifiers.insert(FORMAT_ID__GRADE, "Grade");
global_format_identifiers.insert(FORMAT_ID__ALTITUDE_OLD, "Altitude");
global_format_identifiers.insert(FORMAT_ID__RAW_DATA, "Raw Data");
global_format_identifiers.insert(FORMAT_ID__TEMPERATURE, "Temperature");
global_format_identifiers.insert(FORMAT_ID__INTERVAL_NUM, "Interval number");
global_format_identifiers.insert(FORMAT_ID__DROPOUT_FLAGS, "Dropout flags");
global_format_identifiers.insert(FORMAT_ID__RAW_DATA_FORMAT, "Raw Data Format ID");
global_format_identifiers.insert(FORMAT_ID__RAW_BARO_SENSOR, "Raw Baro Sensor Value");
global_format_identifiers.insert(FORMAT_ID__ALTITUDE, "Altitude");
global_format_identifiers.insert(FORMAT_ID__THRESHOLD_POWER, "Threshold power");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_41, "Unknow 41");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_42, "Unknow 42");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_43, "Unknow 43");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_44, "Unknow 44");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_45, "Unknow 45");
global_format_identifiers.insert(FORMAT_ID__POWER_PEDAL_BALANCE, "Power Pedal Balance");
global_format_identifiers.insert(FORMAT_ID__UNKNOW_47, "Unknow 47");
}
}
int read_byte(int *count = NULL, int *sum = NULL) {
char c;
if (file.read(&c, 1) != 1)
return -1;
if (sum)
*sum += (0xff & c);
if (count)
*count += 1;
return 0xff & c;
}
int read_double_byte(int *count = NULL, int *sum = NULL) {
char c1,c2;
if (file.read(&c1, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c2, 1) != 1)
return -1;
if (count)
*count += 1;
if (sum)
*sum += (0xff & c1) + (0xff & c2);
return 256*(0xff & c1) + (0xff & c2);
}
int read_four_byte(int *count = NULL, int *sum = NULL) {
char c1,c2,c3,c4;
if (file.read(&c1, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c2, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c3, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c4, 1) != 1)
return -1;
if (count)
*count += 1;
if (sum)
*sum += (0xff & c1) + (0xff & c2) + (0xff & c3) + (0xff & c4);
return 256*256*256*(0xff & c1) + 256*256*(0xff & c2) + 256*(0xff & c3) + (0xff & c4);
}
int read_date(int *count = NULL, int *sum = NULL) {
char c1,c2,c3,c4,c5,c6,c7;
if (file.read(&c1, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c2, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c3, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c4, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c5, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c6, 1) != 1)
return -1;
if (count)
*count += 1;
if (file.read(&c7, 1) != 1)
return -1;
if (count)
*count += 1;
if (sum)
*sum += (0xff & c1) + (0xff & c2) + (0xff & c3) + (0xff & c4) + (0xff & c5) + (0xff & c6) + (0xff & c7);
QDateTime dateTime(QDate((0xff & c1)*256+(0xff & c2), (0xff & c3), (0xff & c4)), QTime((0xff & c5), (0xff & c6), (0xff & c7)), Qt::LocalTime);
return dateTime.toTime_t();
}
void decodeMetaData(const BinDefinition &def, const std::vector<int> values) {
int i = 0;
QString deviceInfo = "";
QDateTime t;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
int value = values[i++];
switch (field.id) {
case FORMAT_ID__RIDE_START : {
start_time = value;
t.setTime_t(value);
rideFile->setStartTime(t);
break;
}
case FORMAT_ID__RIDE_SAMPLE_RATE :
rideFile->setRecIntSecs(value/1000.0);
break;
case FORMAT_ID__FIRMWARE_VERSION :
//rideFile->setDeviceType(rideFile->deviceType()+ QString(" (%1)").arg(value));
deviceInfo += rideFile->deviceType()+QString(" Version %1\n").arg(value);
break;
case FORMAT_ID__LAST_UPDATE :
//t.setTime_t(value);
//deviceInfo += QString("Last update %1\n").arg(t.toString());
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__ODOMETER :
if (value>0)
deviceInfo += QString("Odometer %1km\n").arg(value/10.0);
break;
case FORMAT_ID__PRIMARY_POWER_ID :
if (value>0)
deviceInfo += QString("Primary Power Id %1\n").arg(value);
break;
case FORMAT_ID__SECONDARY_POWER_ID :
if (value>0)
deviceInfo += QString("Secondary Power Id %1\n").arg(value);
break;
case FORMAT_ID__CHEST_STRAP_ID :
if (value>0)
deviceInfo += QString("Chest strap Id %1\n").arg(value);
break;
case FORMAT_ID__CADENCE_ID :
if (value>0)
deviceInfo += QString("Cadence Id %1\n").arg(value);
break;
case FORMAT_ID__SPEED_ID :
if (value>0)
deviceInfo += QString("Speed Id %1\n").arg(value);
break;
case FORMAT_ID__RESISTANCE_UNITID :
if (value>0)
deviceInfo += QString("Resistance Unit Id %1\n").arg(value);
break;
case FORMAT_ID__WORKOUT_ID :
// Commented out since value is numeric and in no way
// represents a Workout Code.
//rideFile->setTag("Workout Code", QString(value));
break;
case FORMAT_ID__USER_WEIGHT :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_CATEGORY :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_HR_ZONE_1 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_HR_ZONE_2 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_HR_ZONE_3 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_HR_ZONE_4 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_POWER_ZONE_1 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_POWER_ZONE_2 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_POWER_ZONE_3 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_POWER_ZONE_4 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__USER_POWER_ZONE_5 :
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__WHEEL_CIRC :
deviceInfo += QString("Wheel Circ. %1mm\n").arg(value);
break;
case FORMAT_ID__THRESHOLD_POWER :
deviceInfo += QString("Threshold Power %1W\n").arg(value);
break;
case FORMAT_ID__UNKNOW_41 :
break;
case FORMAT_ID__UNKNOW_42 :
break;
case FORMAT_ID__UNKNOW_43 :
break;
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
rideFile->setTag("Device Info", deviceInfo);
}
void decodeRideData(const BinDefinition &def, const std::vector<int> values) {
int i = 0;
double secs = 0, alt = 0, cad = 0, km = 0, grade = 0, hr = 0;
double nm = 0, kph = 0, watts = 0;
double temperature = RideFile::NA;
double lrbalance = RideFile::NA;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
int value = values[i++];
switch (field.id) {
case FORMAT_ID__RIDE_DISTANCE :
km = value/10000.0;
break;
case FORMAT_ID__RIDE_TIME :
secs = value / 1000.0;
break;
case FORMAT_ID__POWER :
if (value <= 2999) // Limit from definition
watts = value;
break;
case FORMAT_ID__TORQUE :
if (value < 4095) // no value
nm = value;
break;
case FORMAT_ID__SPEED :
kph = value*3.6/100.0;
if (kph > 145) // Limit for data error
kph = 0;
break;
case FORMAT_ID__CADENCE :
if (value < 255) // Limit for data error
cad = value;
break;
case FORMAT_ID__HEART_RATE :
if (value < 255) // Limit for data error
hr = value;
break;
case FORMAT_ID__GRADE :
if (value>37768)
value = value - 256*256;
grade = value/100.0;
break;
case FORMAT_ID__ALTITUDE :
alt = value/10.0;
break;
case FORMAT_ID__ALTITUDE_OLD :
if (alt == 0.0)
alt = value/10.0;
break;
case FORMAT_ID__UNKNOW_44 :
//qDebug() << "44 : " << value;
break;
case FORMAT_ID__UNKNOW_45 :
//qDebug() << "45 : " << value;
break;
case FORMAT_ID__POWER_PEDAL_BALANCE :
if (value < 0xff) { // no value
int pedalIndex = (0x40 & value);
value = (0x3f & value);
if (pedalIndex == 0x40)
value = 100-value;
lrbalance = value;
}
break;
case FORMAT_ID__UNKNOW_47 :
//qDebug() << "47 : " << value;
break;
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
double headwind = 0.0;
int interval = 0;
int lng = 0;
int lat = 0;
// the 0.0 values are L and R torque efficiency and pedal smoothness which are not available in this format
rideFile->appendPoint(secs, cad, hr, km, kph, nm, watts, alt, lng, lat, headwind, grade, temperature, 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);
}
void decodeSparseData(const BinDefinition &def, const std::vector<int> values) {
int i = 0;
double secs = 0.0;
int temperature_count = 0;
double temperature = 0.0;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
int value = values[i++];
switch (field.id) {
case FORMAT_ID__TEMPERATURE :
// use for average
temperature += value/10.0;
temperature_count ++;
rideFile->dataPoints().at(rideFile->timeIndex(secs))->temp = temperature;
rideFile->setDataPresent(RideFile::temp, true);
break;
case FORMAT_ID__RIDE_TIME :
secs = value / 1000.0;
break;
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
rideFile->setTag("Temperature", QString("%1").arg(temperature/temperature_count));
}
void decodeIntervalData(const BinDefinition &def, const std::vector<int> values) {
int i = 0;
double secs = 0;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
int value = values[i++];
switch (field.id) {
case FORMAT_ID__INTERVAL_NUM :
interval = value;
break;
case FORMAT_ID__RIDE_TIME :
secs = value / 1000.0;;
break;
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
if (interval>1) {
rideFile->addInterval(RideFileInterval::DEVICE, last_interval_secs, secs, QString("%1").arg(interval-1), Qt::black);
}
last_interval_secs = secs;
}
void decodeDataError(const BinDefinition &def, const std::vector<int> values) {
int i = 0;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
int value = values[i++];
bool b0 = (value % 2);
bool b1 = (value / 2>1);
bool b2 = (value / 4>1);
bool b3 = (value / 8>1);
bool b4 = (value / 16>1);
bool b5 = (value / 32>1);
bool b6 = (value / 64>1);
bool b7 = (value / 128>1);
QString b = QString("DataError : %1 %2 %3 %4 %5 %6 %7 %8").arg(b0?"0":"").arg(b1?"1":"").arg(b2?"2":"").arg(b3?"3":"").arg(b4?"4":"").arg(b5?"5":"").arg(b6?"6":"").arg(b7?"7":"");
switch (field.id) {
case FORMAT_ID__DROPOUT_FLAGS :
//errors << QString("DataError field.id %1 value %2").arg(field.id).arg(b);
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
case FORMAT_ID__RIDE_TIME :
//errors << QString("DataError time field.id %1 value %2").arg(field.id).arg(value/1000);
unused_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
break;
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
}
void decodeHistoryData(const BinDefinition &def, const std::vector<int> /*values*/) {
//int i = 0;
foreach(const BinField &field, def.fields) {
if (!global_format_identifiers.contains(field.id)) {
unknown_format_identifiers.insert(field.id);
} else {
//int value = values[i++];
switch (field.id) {
default:
unexpected_format_identifiers_for_record_types[def.format_identifier].insert(field.id);
}
}
}
}
int read_record(bool &stop, QStringList &errors) {
int sum = 0;
int bytes_read = 0;
int record_type = read_byte(&bytes_read, &sum); // Always 0xFF
if (record_type == -1) {
errors << QString("Truncated file");
//bytes_read++;
return bytes_read;
} else if (record_type == 255) {
int format_identifier = read_byte(&bytes_read, &sum);
if (format_identifier == -1) {
errors << QString("Truncated file");
//bytes_read++;
return bytes_read;
} else if (!global_record_types.contains(format_identifier)) {
errors << QString("unknown format_identifier %1").arg(format_identifier);
stop = true;
return bytes_read;
}
int nb_meta = read_double_byte(&bytes_read, &sum);
local_format_identifiers.insert(format_identifier, BinDefinition());
//printf("- format_identifier : %d\n", format_identifier);
BinDefinition &def = local_format_identifiers[format_identifier];
def.format_identifier = format_identifier;
for (int i = 0; i < nb_meta; ++i) {
def.fields.push_back(BinField());
BinField &field = def.fields.back();
int field_id = read_double_byte(&bytes_read,&sum);
field.id = field_id;
int field_size = read_double_byte(&bytes_read,&sum);
field.size = field_size;
//printf("- field %d : %d\n", field_id, field_size);
}
int checksum = read_double_byte(&bytes_read);
//printf("- checksum %d : %d\n", checksum, sum);
if (checksum == -1) {
errors << QString("Truncated file");
return bytes_read;
} else if (checksum != sum) {
errors << QString("bad checksum: %1").arg(sum);
stop = true;
return bytes_read;
}
}
else {
int format_identifier = record_type;
//printf("- data for format identifier : %d\n", format_identifier);
if (!local_format_identifiers.contains(format_identifier)) {
errors << QString("undefined format_identifier: %1").arg(format_identifier);
stop = true;
return bytes_read;
} else {
const BinDefinition &def = local_format_identifiers[format_identifier];
//printf("- fields type : %d\n", def.format_identifier);
std::vector<int> values;
foreach(const BinField &field, def.fields) {
//printf("- field : %d \n", field.id);
int v;
switch (field.size) {
case 1: v = read_byte(&bytes_read,&sum); break;
case 2: v = read_double_byte(&bytes_read,&sum); break;
case 4: v = read_four_byte(&bytes_read,&sum); break;
case 7: v = read_date(&bytes_read,&sum); break;
default:
for (int i = 0; i < field.size; ++i)
read_byte(&bytes_read,&sum);
errors << QString("unsupported field size %1").arg(field.size);
}
values.push_back(v);
//printf("- %d : %d\n", field.id, v);
}
int checksum = read_double_byte(&bytes_read);
//printf("- checksum %d : %d\n", checksum, sum);
if (checksum == -1) {
errors << QString("Truncated file");
return bytes_read;
} else if (checksum != sum) {
errors << QString("bad checksum: %1").arg(sum);
stop = true;
return bytes_read;
}
switch (def.format_identifier) {
case RECORD_TYPE__META: decodeMetaData(def, values); break;
case RECORD_TYPE__RIDE_DATA: decodeRideData(def, values); break;
case RECORD_TYPE__RAW_DATA:
unused_record_types.insert(def.format_identifier);
break;
case RECORD_TYPE__SPARSE_DATA: decodeSparseData(def, values); break;
case RECORD_TYPE__INTERVAL_DATA: decodeIntervalData(def, values); break;
case RECORD_TYPE__DATA_ERROR: decodeDataError(def, values); break;
case RECORD_TYPE__HISTORY: decodeHistoryData(def, values); break;
default:
unexpected_record_types.insert(def.format_identifier);
}
}
}
return bytes_read;
}
RideFile * run() {
errors.clear();
rideFile = new RideFile;
rideFile->setDeviceType("Joule");
rideFile->setFileFormat("CycleOps Joule (bin)");
if (!file.open(QIODevice::ReadOnly)) {
delete rideFile;
return NULL;
}
//
bool stop = false;
int data_size = file.size();
int bytes_read = 0;
while (!stop && (bytes_read < data_size)) {
bytes_read += read_record(stop, errors);
}
if (last_interval_secs>0) {
rideFile->addInterval(RideFileInterval::DEVICE, last_interval_secs, rideFile->dataPoints().last()->secs, QString("%1").arg(interval));
}
if (stop) {
file.close();
delete rideFile;
return NULL;
}
else {
file.close();
foreach(int num, unknown_record_types) {
errors << QString("unknow record type %1; ignoring it").arg(num);
}
foreach(int num, unknown_format_identifiers) {
errors << QString("unknow format identifier %1; ignoring it").arg(num);
}
/*foreach(int num, unused_record_types) {
errors << QString("unused record type \"%1\" (%2)\n").arg(global_record_types[num].toAscii().constData())
.arg(num);
}
foreach(QSet<int> set, unused_format_identifiers_for_record_types) {
foreach(int num, set) {
int record_type = unused_format_identifiers_for_record_types.keys(set).takeFirst();
errors << QString("unused format identifier \"%1\" (%2) in \"%3\" (%4)\n")
.arg(global_format_identifiers[num].toAscii().constData())
.arg(num)
.arg(global_record_types[record_type].toAscii().constData())
.arg(record_type);
}
}*/
foreach(int num, unexpected_record_types) {
errors << QString("unexpected record type %1 (%2)\n").arg(global_record_types[num]).arg(num);
}
foreach(QSet<int> set, unexpected_format_identifiers_for_record_types) {
foreach(int num, set) {
int record_type = unexpected_format_identifiers_for_record_types.keys(set).takeFirst();
errors << QString("unexpected format identifier \"%1\" (%2) in \"%3\" (%4)\n")
.arg(global_format_identifiers[num].toLatin1().constData())
.arg(num)
.arg(global_record_types[record_type].toLatin1().constData())
.arg(record_type);
}
}
return rideFile;
}
}
};
QMap<int,QString> BinFileReaderState::global_record_types;
QMap<int,QString> BinFileReaderState::global_format_identifiers;
RideFile *BinFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
{
QSharedPointer<BinFileReaderState> state(new BinFileReaderState(file, errors));
return state->run();
}