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; }
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; }