mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 08:38:45 +00:00
.. read from FIT/TCX .. write to JSON .. view in Editor NOTE: They are not on any of the charts yet.
251 lines
7.5 KiB
C++
251 lines
7.5 KiB
C++
/*
|
|
* Copyright (c) 2011 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 "SyncRideFile.h"
|
|
#include <QSharedPointer>
|
|
#include <QMap>
|
|
#include <QSet>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <math.h>
|
|
|
|
|
|
static int syncFileReaderRegistered =
|
|
RideFileFactory::instance().registerReader(
|
|
"osyn", "Macro GoldenCheetah Sync File", new SyncFileReader());
|
|
|
|
|
|
#define TRAINING_DETAIL 0x89 //Packet with details about a specific training
|
|
|
|
|
|
struct SyncFileReaderState
|
|
{
|
|
QFile &file;
|
|
QStringList &errors;
|
|
RideFile *rideFile;
|
|
|
|
double secs, km;
|
|
|
|
int interval;
|
|
double last_interval_secs;
|
|
|
|
bool stopped;
|
|
|
|
|
|
SyncFileReaderState(QFile &file, QStringList &errors) :
|
|
file(file), errors(errors), rideFile(NULL), secs(0), km(0),
|
|
interval(0), last_interval_secs(0.0), stopped(true)
|
|
{
|
|
|
|
}
|
|
|
|
struct TruncatedRead {};
|
|
|
|
int read_bsd_byte(int *count = NULL, int *sum = NULL) {
|
|
char c;
|
|
if (file.read(&c, 1) != 1)
|
|
throw TruncatedRead();
|
|
if (sum)
|
|
*sum += (0xff & c);
|
|
if (count)
|
|
*count += 1;
|
|
|
|
return (0xff & c) - (int(c/16)*6);
|
|
}
|
|
|
|
int read_bytes(int len, int *count = NULL, int *sum = NULL) {
|
|
char c;
|
|
int res = 0;
|
|
for (int i = 0; i < len; ++i) {
|
|
if (file.read(&c, 1) != 1)
|
|
throw TruncatedRead();
|
|
if (sum)
|
|
*sum += (0xff & c);
|
|
if (count)
|
|
*count += 1;
|
|
|
|
res += pow(256,i) * (0xff & c);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
QString
|
|
cEscape(char *buf, int len)
|
|
{
|
|
char *result = new char[4 * len + 1];
|
|
char *tmp = result;
|
|
for (int i = 0; i < len; ++i) {
|
|
if (buf[i] == '"')
|
|
tmp += sprintf(tmp, "\\\"");
|
|
else if (isprint(buf[i]))
|
|
*(tmp++) = buf[i];
|
|
else
|
|
tmp += sprintf(tmp, "\\x%02x", 0xff & (unsigned) buf[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void read_graph_data(double *secs, int *count = NULL, int *sum = NULL)
|
|
{
|
|
double alt = 0, cad = 0, grade = 0, hr = 0, kph = 0, watts = 0;
|
|
|
|
double nm = 0, headwind = 0.0, temp = 0.0;
|
|
int lng = 0, lat = 0;
|
|
|
|
// Graph data (12 bytes)
|
|
|
|
temp = read_bytes(2, count, sum) / 100; // Temperature * 100 (in degree C)
|
|
|
|
alt = read_bytes(2, count, sum); //Alti (in m)
|
|
grade = read_bytes(1, count, sum); // Gradient %
|
|
cad = read_bytes(1, count, sum); // Cadence
|
|
kph = read_bytes(2, count, sum)/10.0; // Speed * 10 (in Km/h)
|
|
hr = read_bytes(1, count, sum); // Heart Rate
|
|
watts = read_bytes(2, count, sum); // Power (in Watt)
|
|
|
|
int record_data_flag = read_bytes(1, count, sum); // Data Flag
|
|
|
|
int intSecs = 1;
|
|
if ((record_data_flag & 0x03) == 0)
|
|
intSecs = 5;
|
|
else if ((record_data_flag & 0x03) == 1)
|
|
intSecs = 10;
|
|
else if ((record_data_flag & 0x03) == 2)
|
|
intSecs = 20;
|
|
|
|
km += intSecs * kph / 3600.0;
|
|
rideFile->setRecIntSecs(intSecs);
|
|
rideFile->appendPoint(*secs, cad, hr, km, kph, nm, watts, alt, lng, lat, headwind, grade, temp, 0.0,
|
|
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, interval);
|
|
|
|
*secs = *secs + intSecs;
|
|
|
|
}
|
|
|
|
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 * run() {
|
|
errors.clear();
|
|
rideFile = new RideFile;
|
|
rideFile->setDeviceType("o_synce macro/macrox");
|
|
rideFile->setFileFormat("Macro GoldenCheetah Sync File (osyn)");
|
|
|
|
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_page(); // read_page(stop, errors);
|
|
}
|
|
|
|
if (stop) {
|
|
file.close();
|
|
delete rideFile;
|
|
return NULL;
|
|
|
|
} else {
|
|
file.close();
|
|
return rideFile;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
RideFile *SyncFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
|
|
{
|
|
QSharedPointer<SyncFileReaderState> state(new SyncFileReaderState(file, errors));
|
|
return state->run();
|
|
}
|