Example #1
0
int main(int argc, char *argv[])
{
    uint32_t formats = 0;
    int pipe_mode = 0;
    int set_laps = 0;
    int download_elevation = 1;
    char *lap_definitions = 0;
    FILE *input_file = 0;
    TTBIN_FILE *ttbin = 0;
    unsigned i;

    int opt = 0;
    int option_index = 0;

    /* create the options lists */
    #define OPTION_COUNT    (OFFLINE_FORMAT_COUNT + 5)
    struct option long_options[OPTION_COUNT] =
    {
        { "help", no_argument, 0, 'h' },
        { "all",  no_argument, 0, 'a' },
        { "laps", required_argument, 0, 'l' },
        { "no-elevation", no_argument, 0, 'E' },
    };
    char short_options[OPTION_COUNT + 1] = "hl:aE";

    opt = 4;
    for (i = 0; i < OFFLINE_FORMAT_COUNT; ++i)
    {
        if (OFFLINE_FORMATS[i].producer)
        {
            long_options[opt].name    = OFFLINE_FORMATS[i].name;
            long_options[opt].has_arg = no_argument;
            long_options[opt].flag    = 0;
            long_options[opt].val     = OFFLINE_FORMATS[i].name[0];

            short_options[opt++ + 1]  = OFFLINE_FORMATS[i].name[0];
        }
    }
    while (opt < OPTION_COUNT)
    {
        memset(&long_options[opt], 0, sizeof(struct option));
        short_options[opt++ + 1] = 0;
    }

    /* check the command line options */
    while ((opt = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1)
    {
        switch (opt)
        {
        case 'h':   /* help */
            help(argv);
            return 0;
        case 'l':   /* set lap list */
            set_laps = 1;
            lap_definitions = optarg;
            break;
        case 'a':   /* all supported formats */
            formats = 0xffffffff;
            break;
        case 'E':   /* no elevation */
            download_elevation = 0;
            break;
        default:
            for (i = 0; i < OFFLINE_FORMAT_COUNT; ++i)
            {
                if (opt == OFFLINE_FORMATS[i].name[0])
                {
                    formats |= OFFLINE_FORMATS[i].mask;
                    break;
                }
            }
            break;
        }
    }

    /* check that we actually have to do something */
    if (!formats)
    {
        help(argv);
        return 0;
    }

    pipe_mode = (optind >= argc);

    /* make sure we've only got one output format specified if we're operating as a pipe */
    if (pipe_mode && (formats & (formats - 1)))
    {
        fprintf(stderr, "Only one output format can be specified in pipe mode\n");
        return 4;
    }

    /* open the input file */
    if (!pipe_mode)
    {
        input_file = fopen(argv[optind], "r");
        if (!input_file)
        {
            fprintf(stderr, "Unable to open input file: %s\n", argv[optind]);
            return 3;
        }
    }
    else
        input_file = stdin;

    /* read the ttbin data file */
    ttbin = read_ttbin_file(input_file);
    if (input_file != stdin)
        fclose(input_file);
    if (!ttbin)
    {
        fprintf(stderr, "Unable to read and parse TTBIN file\n");
        return 5;
    }

    /* if we have gps data, download the elevation data */
    if (ttbin->gps_records.count && download_elevation)
        download_elevation_data(ttbin);

    /* set the list of laps if we have been asked to */
    if (set_laps)
        do_replace_lap_list(ttbin, lap_definitions);

    /* write the output files */
    for (i = 0; i < OFFLINE_FORMAT_COUNT; ++i)
    {
        if ((formats & OFFLINE_FORMATS[i].mask) && OFFLINE_FORMATS[i].producer)
        {
            if ((OFFLINE_FORMATS[i].gps_ok && ttbin->gps_records.count)
                || (OFFLINE_FORMATS[i].treadmill_ok && ttbin->activity==ACTIVITY_TREADMILL)
                || (OFFLINE_FORMATS[i].pool_swim_ok && ttbin->activity==ACTIVITY_SWIMMING))
            {
                FILE *output_file = stdout;
                if (!pipe_mode)
                {
                    const char *filename = create_filename(ttbin, OFFLINE_FORMATS[i].name);
                    output_file = fopen(filename, "w");
                    if (!output_file)
                        fprintf(stderr, "Unable to create output file: %s\n", filename);
                }
                if (output_file)
                {
                    (*OFFLINE_FORMATS[i].producer)(ttbin, output_file);

                    if (output_file != stdout)
                        fclose(output_file);
                }
            }
            else
                fprintf(stderr, "Unable to process output format: %s\n", OFFLINE_FORMATS[i].name);
        }
    }

    free_ttbin(ttbin);

    return 0;
}
Example #2
0
TTBIN_FILE *parse_ttbin_data(uint8_t *data, uint32_t size)
{
    const uint8_t *const end = data + size;
    TTBIN_FILE *file;
    unsigned length;

    FILE_HEADER               *file_header = 0;
    union
    {
        uint8_t *data;
        struct
        {
            uint8_t tag;
            union
            {
                FILE_SUMMARY_RECORD         summary;
                FILE_GPS_RECORD             gps;
                FILE_HEART_RATE_RECORD      heart_rate;
                FILE_STATUS_RECORD          status;
                FILE_TREADMILL_RECORD       treadmill;
                FILE_SWIM_RECORD            swim;
                FILE_LAP_RECORD             lap;
                FILE_RACE_SETUP_RECORD      race_setup;
                FILE_RACE_RESULT_RECORD     race_result;
                FILE_TRAINING_SETUP_RECORD  training_setup;
                FILE_GOAL_PROGRESS_RECORD   goal_progress;
                FILE_INTERVAL_SETUP_RECORD  interval_setup;
                FILE_INTERVAL_START_RECORD  interval_start;
                FILE_INTERVAL_FINISH_RECORD interval_finish;
            };
        } *record;
    } p;

    TTBIN_RECORD *record;

    /* check that the file is long enough */
    if (size < (sizeof(FILE_HEADER) - sizeof(RECORD_LENGTH)))
        return 0;

    if (*data++ != TAG_FILE_HEADER)
        return 0;

    file = malloc(sizeof(TTBIN_FILE));
    memset(file, 0, sizeof(TTBIN_FILE));

    file_header = (FILE_HEADER*)data;
    data += sizeof(FILE_HEADER) + (file_header->length_count - 1) * sizeof(RECORD_LENGTH);
    file->file_version    = file_header->file_version;
    memcpy(file->firmware_version, file_header->firmware_version, sizeof(file->firmware_version));
    file->product_id      = file_header->product_id;
    file->timestamp_local = file_header->timestamp;
    file->timestamp_utc   = file_header->timestamp - file_header->local_time_offset;
    file->utc_offset      = file_header->local_time_offset;

    for (p.data = data; p.data < end; p.data += length)
    {
        unsigned index = 0;

        /* find the length of this tag */
        while ((index < file_header->length_count) && (file_header->lengths[index].tag < p.record->tag))
            ++index;
        if ((index < file_header->length_count) && (file_header->lengths[index].tag == p.record->tag))
            length = file_header->lengths[index].length;
        else
        {
            free_ttbin(file);
            return 0;
        }

        switch (p.record->tag)
        {
        case TAG_SUMMARY:
            file->activity       = p.record->summary.activity;
            file->total_distance = p.record->summary.distance;
            file->duration       = p.record->summary.duration;
            file->total_calories = p.record->summary.calories;
            break;
        case TAG_STATUS:
            p.record->status.timestamp -= file->utc_offset;

            record = append_record(file, p.record->tag, length);
            record->status.status = p.record->status.status;
            record->status.activity = p.record->status.activity;
            record->status.timestamp = p.record->status.timestamp;
            append_array(&file->status_records, record);
            break;
        case TAG_GPS:
            /* if the GPS signal is lost, 0xffffffff is stored in the file */
            if (p.record->gps.timestamp == 0xffffffff)
                break;

            record = append_record(file, p.record->tag, length);
            record->gps.latitude     = p.record->gps.latitude / 1e7;
            record->gps.longitude    = p.record->gps.longitude / 1e7;
            record->gps.elevation    = 0.0f;
            record->gps.heading      = p.record->gps.heading / 100.0f;
            record->gps.speed        = p.record->gps.speed / 100.0f;
            record->gps.timestamp    = p.record->gps.timestamp;
            record->gps.calories     = p.record->gps.calories;
            record->gps.inc_distance = p.record->gps.inc_distance;
            record->gps.cum_distance = p.record->gps.cum_distance;
            record->gps.cycles       = p.record->gps.cycles;
            append_array(&file->gps_records, record);
            break;
        case TAG_HEART_RATE:
            p.record->heart_rate.timestamp -= file->utc_offset;

            record = append_record(file, p.record->tag, length);
            record->heart_rate.timestamp  = p.record->heart_rate.timestamp;
            record->heart_rate.heart_rate = p.record->heart_rate.heart_rate;
            append_array(&file->heart_rate_records, record);
            break;
        case TAG_LAP:
            record = append_record(file, p.record->tag, length);
            record->lap.total_time     = p.record->lap.total_time;
            record->lap.total_distance = p.record->lap.total_distance;
            record->lap.total_calories = p.record->lap.total_calories;
            append_array(&file->lap_records, record);
            break;
        case TAG_TREADMILL:
            p.record->treadmill.timestamp -= file->utc_offset;

            record = append_record(file, p.record->tag, length);
            record->treadmill.timestamp = p.record->treadmill.timestamp;
            record->treadmill.distance  = p.record->treadmill.distance;
            record->treadmill.calories  = p.record->treadmill.calories;
            record->treadmill.steps     = p.record->treadmill.steps;
            append_array(&file->treadmill_records, record);
            break;
        case TAG_SWIM:
            p.record->swim.timestamp -= file->utc_offset;

            record = append_record(file, p.record->tag, length);
            record->swim.timestamp      = p.record->swim.timestamp;
            record->swim.total_distance = p.record->swim.total_distance;
            record->swim.strokes        = p.record->swim.strokes;
            record->swim.completed_laps = p.record->swim.completed_laps;
            record->swim.total_calories = p.record->swim.total_calories;
            append_array(&file->swim_records, record);
            break;
        case TAG_RACE_SETUP:
            record = append_record(file, p.record->tag, length);
            record->race_setup.distance = p.record->race_setup.distance;
            record->race_setup.duration = p.record->race_setup.duration;
            memcpy(record->race_setup.name, p.record->race_setup.name, sizeof(p.record->race_setup.name));
            file->race_setup = record;
            break;
        case TAG_RACE_RESULT:
            if (!file->race_setup)
            {
                free_ttbin(file);
                return 0;
            }
            record = append_record(file, p.record->tag, length);
            record->race_result.distance = p.record->race_result.distance;
            record->race_result.duration = p.record->race_result.duration;
            record->race_result.calories = p.record->race_result.calories;
            file->race_result = record;
            break;
        case TAG_TRAINING_SETUP:
            record = append_record(file, p.record->tag, length);
            record->training_setup.type      = p.record->training_setup.type;
            record->training_setup.value_min = p.record->training_setup.min;
            record->training_setup.max       = p.record->training_setup.max;
            file->training_setup = record;
            break;
        case TAG_GOAL_PROGRESS:
            record = append_record(file, p.record->tag, length);
            record->goal_progress.percent = p.record->goal_progress.percent;
            record->goal_progress.value   = p.record->goal_progress.value;
            append_array(&file->goal_progress_records, record);
            break;
        case TAG_INTERVAL_SETUP:
            record = append_record(file, p.record->tag, length);
            record->interval_setup.warm_type = p.record->interval_setup.warm_type;
            record->interval_setup.warm      = p.record->interval_setup.warm;
            record->interval_setup.work_type = p.record->interval_setup.work_type;
            record->interval_setup.work      = p.record->interval_setup.work;
            record->interval_setup.rest_type = p.record->interval_setup.rest_type;
            record->interval_setup.rest      = p.record->interval_setup.rest;
            record->interval_setup.cool_type = p.record->interval_setup.cool_type;
            record->interval_setup.cool      = p.record->interval_setup.cool;
            record->interval_setup.sets      = p.record->interval_setup.sets;
            file->interval_setup = record;
            break;
        case TAG_INTERVAL_START:
            record = append_record(file, p.record->tag, length);
            record->interval_start.type = p.record->interval_start.type;
            append_array(&file->interval_start_records, record);
            break;
        case TAG_INTERVAL_FINISH:
            record = append_record(file, p.record->tag, length);
            record->interval_finish.type           = p.record->interval_finish.type;
            record->interval_finish.total_time     = p.record->interval_finish.total_time;
            record->interval_finish.total_distance = p.record->interval_finish.total_distance;
            record->interval_finish.total_calories = p.record->interval_finish.total_calories;
            append_array(&file->interval_finish_records, record);
            break;
        default:
            record = append_record(file, p.record->tag, length);
            memcpy(record->data, p.data + 1, length - 1);
            break;
        }
    }

    return file;
}