From c8e52081cdeb51ec21026ece7c68395fcdcd49b2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 29 Aug 2018 15:55:48 -0700 Subject: [PATCH] garmin: actually start using the parsed data This gets me real profiles, with depth and temperature information. Sadly, the temperature data seems to be in whole degrees C, which is not good for diving. But certainly not unheard of. Also, while this does actually transfer a lot of other information too, there are certainly things missing. No gas information is gathered (although we do parse it, we just don't save it), and none of the events are parsed at all. And the GPS information that we have isn't passed on yet, because there are no libdivecomputer interfaces to do that. I'll have to come up with something. But it's actually almost useful. All the basics seem to be there. How *buggy* it is, I do not know, but the profiles don't look obviously broken. Signed-off-by: Linus Torvalds --- src/garmin_parser.c | 281 +++++++++++++++++++++++++++----------------- 1 file changed, 172 insertions(+), 109 deletions(-) diff --git a/src/garmin_parser.c b/src/garmin_parser.c index f9e123a..07c8c82 100644 --- a/src/garmin_parser.c +++ b/src/garmin_parser.c @@ -48,13 +48,26 @@ struct type_desc { typedef struct garmin_parser_t { dc_parser_t base; + + dc_sample_callback_t callback; + void *userdata; + + // Some sample data needs to be bunched up + // and sent together. + struct { + unsigned int time; + int stop_time; + double ceiling; + } sample_data; struct type_desc type_desc[MAXTYPE]; + // Field cache struct { unsigned int initialized; unsigned int protocol; unsigned int profile; - unsigned int utc_offset, time_offset; + unsigned int time; + int utc_offset, time_offset; unsigned int divetime; double maxdepth; double avgdepth; @@ -75,6 +88,23 @@ typedef struct garmin_parser_t { typedef int (*garmin_data_cb_t)(unsigned char type, const unsigned char *data, int len, void *user); +static void flush_pending_sample(struct garmin_parser_t *garmin) +{ + if (!garmin->callback) + return; + + if (garmin->sample_data.stop_time && garmin->sample_data.ceiling) { + dc_sample_value_t sample = {0}; + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.time = garmin->sample_data.stop_time; + sample.deco.depth = garmin->sample_data.ceiling; + garmin->callback(DC_SAMPLE_DECO, sample, garmin->userdata); + } + garmin->sample_data.stop_time = 0; + garmin->sample_data.ceiling = 0; +} + + static dc_status_t garmin_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); static dc_status_t garmin_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t garmin_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); @@ -164,110 +194,148 @@ static const struct { */ struct field_desc { const char *name; - int (*parse)(struct garmin_parser_t *, unsigned char base_type, const unsigned char *data); + void (*parse)(struct garmin_parser_t *, unsigned char base_type, const unsigned char *data); }; #define DECLARE_FIELD(msg, name, type) __DECLARE_FIELD(msg##_##name, type) #define __DECLARE_FIELD(name, type) \ - static int parse_##name(struct garmin_parser_t *, const type); \ - static int parse_##name##_##type(struct garmin_parser_t *g, unsigned char base_type, const unsigned char *p) \ + static void parse_##name(struct garmin_parser_t *, const type); \ + static void parse_##name##_##type(struct garmin_parser_t *g, unsigned char base_type, const unsigned char *p) \ { \ if (strcmp(#type, base_type_info[base_type].type_name)) \ fprintf(stderr, "%s: %s should be %s\n", #name, #type, base_type_info[base_type].type_name); \ type val = *(type *)p; \ - if (val == type##_INVAL) return 0; \ + if (val == type##_INVAL) return; \ DEBUG(g->base.context, "%s (%s): %lld", #name, #type, (long long)val); \ - return parse_##name(g, *(type *)p); \ + parse_##name(g, *(type *)p); \ } \ static const struct field_desc name##_field_##type = { #name, parse_##name##_##type }; \ - static int parse_##name(struct garmin_parser_t *garmin, type data) + static void parse_##name(struct garmin_parser_t *garmin, type data) // All msg formats can have a timestamp // Garmin timestamps are in seconds since 00:00 Dec 31 1989 UTC // Convert to "standard epoch time" by adding 631065600. DECLARE_FIELD(ANY, timestamp, UINT32) { - dc_ticks_t time = 631065600 + (dc_ticks_t) data; - dc_datetime_t date; + if (garmin->callback) { + dc_sample_value_t sample = {0}; - // Show local time (time_offset) - dc_datetime_gmtime(&date, time + garmin->cache.time_offset); - DEBUG(garmin->base.context, - "%04d-%02d-%02d %02d:%02d:%02d", - date.year, date.month, date.day, - date.hour, date.minute, date.second); - return 0; + // Turn the timestamp relative to the beginning of the dive + if (data < garmin->cache.time) + return; + data -= garmin->cache.time; + + // Did we already do this? + if (data <= garmin->sample_data.time) + return; + + // Flush any pending sample data before sending the next time event + flush_pending_sample(garmin); + + // *Now* we're ready to actually update the sample times + garmin->sample_data.time = data; + sample.time = data; + garmin->callback(DC_SAMPLE_TIME, sample, garmin->userdata); + } } -DECLARE_FIELD(ANY, message_index, UINT16) { return 0; } -DECLARE_FIELD(ANY, part_index, UINT32) { return 0; } +DECLARE_FIELD(ANY, message_index, UINT16) { } +DECLARE_FIELD(ANY, part_index, UINT32) { } // FILE msg -DECLARE_FIELD(FILE, file_type, ENUM) { return 0; } -DECLARE_FIELD(FILE, manufacturer, UINT16) { return 0; } -DECLARE_FIELD(FILE, product, UINT16) { return 0; } -DECLARE_FIELD(FILE, serial, UINT32Z) { return 0; } -DECLARE_FIELD(FILE, creation_time, UINT32) { return parse_ANY_timestamp(garmin, data); } -DECLARE_FIELD(FILE, number, UINT16) { return 0; } -DECLARE_FIELD(FILE, other_time, UINT32) { return parse_ANY_timestamp(garmin, data); } +DECLARE_FIELD(FILE, file_type, ENUM) { } +DECLARE_FIELD(FILE, manufacturer, UINT16) { } +DECLARE_FIELD(FILE, product, UINT16) { } +DECLARE_FIELD(FILE, serial, UINT32Z) { } +DECLARE_FIELD(FILE, creation_time, UINT32) { } +DECLARE_FIELD(FILE, number, UINT16) { } +DECLARE_FIELD(FILE, other_time, UINT32) { } // SESSION msg -DECLARE_FIELD(SESSION, start_time, UINT32) { return parse_ANY_timestamp(garmin, data); } -DECLARE_FIELD(SESSION, start_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(SESSION, start_pos_long, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(SESSION, nec_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(SESSION, nec_pos_long, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(SESSION, swc_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(SESSION, swc_pos_long, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(SESSION, exit_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(SESSION, exit_pos_long, SINT32) { return 0; } // 180 deg / 2**31 +DECLARE_FIELD(SESSION, start_time, UINT32) { garmin->cache.time = data; } +DECLARE_FIELD(SESSION, start_pos_lat, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(SESSION, start_pos_long, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(SESSION, nec_pos_lat, SINT32) { } // 180 deg / 2**31 NE corner +DECLARE_FIELD(SESSION, nec_pos_long, SINT32) { } // 180 deg / 2**31 pos +DECLARE_FIELD(SESSION, swc_pos_lat, SINT32) { } // 180 deg / 2**31 SW corner +DECLARE_FIELD(SESSION, swc_pos_long, SINT32) { } // 180 deg / 2**31 pos +DECLARE_FIELD(SESSION, exit_pos_lat, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(SESSION, exit_pos_long, SINT32) { } // 180 deg / 2**31 // LAP msg -DECLARE_FIELD(LAP, start_time, UINT32) { return parse_ANY_timestamp(garmin, data); } -DECLARE_FIELD(LAP, start_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(LAP, start_pos_long, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(LAP, end_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(LAP, end_pos_long, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(LAP, some_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(LAP, some_pos_long, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(LAP, other_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(LAP, other_pos_long, SINT32) { return 0; } // 180 deg / 2**31 +DECLARE_FIELD(LAP, start_time, UINT32) { } +DECLARE_FIELD(LAP, start_pos_lat, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(LAP, start_pos_long, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(LAP, end_pos_lat, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(LAP, end_pos_long, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(LAP, some_pos_lat, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(LAP, some_pos_long, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(LAP, other_pos_lat, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(LAP, other_pos_long, SINT32) { } // 180 deg / 2**31 // RECORD msg -DECLARE_FIELD(RECORD, position_lat, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(RECORD, position_long, SINT32) { return 0; } // 180 deg / 2**31 -DECLARE_FIELD(RECORD, altitude, UINT16) { return 0; } // 5 *m + 500 ? -DECLARE_FIELD(RECORD, heart_rate, UINT8) { return 0; } // bpm -DECLARE_FIELD(RECORD, distance, UINT32) { return 0; } // Distance in 100 * m? WTF? -DECLARE_FIELD(RECORD, temperature, SINT8) { return 0; } // degrees C -DECLARE_FIELD(RECORD, abs_pressure, UINT32) {return 0; } // Pascal -DECLARE_FIELD(RECORD, depth, UINT32) { return 0; } // mm -DECLARE_FIELD(RECORD, next_stop_depth, UINT32) { return 0; } // mm -DECLARE_FIELD(RECORD, next_stop_time, UINT32) { return 0; } // seconds -DECLARE_FIELD(RECORD, tts, UINT32) { return 0; } // seconds -DECLARE_FIELD(RECORD, ndl, UINT32) { return 0; } // s -DECLARE_FIELD(RECORD, cns_load, UINT8) { return 0; } // percent -DECLARE_FIELD(RECORD, n2_load, UINT16) { return 0; } // percent +DECLARE_FIELD(RECORD, position_lat, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(RECORD, position_long, SINT32) { } // 180 deg / 2**31 +DECLARE_FIELD(RECORD, altitude, UINT16) { } // 5 *m + 500 ? +DECLARE_FIELD(RECORD, heart_rate, UINT8) { } // bpm +DECLARE_FIELD(RECORD, distance, UINT32) { } // Distance in 100 * m? WTF? +DECLARE_FIELD(RECORD, temperature, SINT8) // degrees C +{ + if (garmin->callback) { + dc_sample_value_t sample = {0}; + sample.temperature = data; + garmin->callback(DC_SAMPLE_TEMPERATURE, sample, garmin->userdata); + } +} +DECLARE_FIELD(RECORD, abs_pressure, UINT32) {} // Pascal +DECLARE_FIELD(RECORD, depth, UINT32) // mm +{ + if (garmin->callback) { + dc_sample_value_t sample = {0}; + sample.depth = data / 1000.0; + garmin->callback(DC_SAMPLE_DEPTH, sample, garmin->userdata); + } +} +DECLARE_FIELD(RECORD, next_stop_depth, UINT32) // mm +{ + garmin->sample_data.ceiling = data / 1000.0; +} +DECLARE_FIELD(RECORD, next_stop_time, UINT32) // seconds +{ + garmin->sample_data.stop_time = data; +} +DECLARE_FIELD(RECORD, tts, UINT32) { } // seconds +DECLARE_FIELD(RECORD, ndl, UINT32) // s +{ + if (garmin->callback) { + dc_sample_value_t sample = {0}; + sample.deco.type = DC_DECO_NDL; + sample.deco.time = data; + garmin->callback(DC_SAMPLE_DECO, sample, garmin->userdata); + } +} +DECLARE_FIELD(RECORD, cns_load, UINT8) { } // percent +DECLARE_FIELD(RECORD, n2_load, UINT16) { } // percent // DEVICE_SETTINGS -DECLARE_FIELD(DEVICE_SETTINGS, utc_offset, UINT32) { garmin->cache.utc_offset = data; return 0; } -DECLARE_FIELD(DEVICE_SETTINGS, time_offset, UINT32) { garmin->cache.time_offset = data; return 0; } +DECLARE_FIELD(DEVICE_SETTINGS, utc_offset, UINT32) { garmin->cache.utc_offset = (SINT32) data; } // wrong type in FIT +DECLARE_FIELD(DEVICE_SETTINGS, time_offset, UINT32) { garmin->cache.time_offset = (SINT32) data; } // wrong type in FIT // DIVE_GAS - uses msg index -DECLARE_FIELD(DIVE_GAS, helium, UINT8) { return 0; } // percent -DECLARE_FIELD(DIVE_GAS, oxygen, UINT8) { return 0; } // percent -DECLARE_FIELD(DIVE_GAS, status, ENUM) { return 0; } // 0 - disabled, 1 - enabled, 2 - backup +DECLARE_FIELD(DIVE_GAS, helium, UINT8) { } // percent +DECLARE_FIELD(DIVE_GAS, oxygen, UINT8) { } // percent +DECLARE_FIELD(DIVE_GAS, status, ENUM) { } // 0 - disabled, 1 - enabled, 2 - backup // DIVE_SUMMARY -DECLARE_FIELD(DIVE_SUMMARY, avg_depth, UINT32) { return 0; } // mm -DECLARE_FIELD(DIVE_SUMMARY, max_depth, UINT32) { return 0; } // mm -DECLARE_FIELD(DIVE_SUMMARY, surface_interval, UINT32) { return 0; } // sec -DECLARE_FIELD(DIVE_SUMMARY, start_cns, UINT8) { return 0; } // percent -DECLARE_FIELD(DIVE_SUMMARY, end_cns, UINT8) { return 0; } // percent -DECLARE_FIELD(DIVE_SUMMARY, start_n2, UINT16) { return 0; } // percent -DECLARE_FIELD(DIVE_SUMMARY, end_n2, UINT16) { return 0; } // percent -DECLARE_FIELD(DIVE_SUMMARY, o2_toxicity, UINT16) { return 0; } // OTUs -DECLARE_FIELD(DIVE_SUMMARY, dive_number, UINT32) { return 0; } -DECLARE_FIELD(DIVE_SUMMARY, bottom_time, UINT32) { return 0; } // ms +DECLARE_FIELD(DIVE_SUMMARY, avg_depth, UINT32) { garmin->cache.avgdepth = data / 1000.0; } // mm +DECLARE_FIELD(DIVE_SUMMARY, max_depth, UINT32) { garmin->cache.maxdepth = data / 1000.0; } // mm +DECLARE_FIELD(DIVE_SUMMARY, surface_interval, UINT32) { } // sec +DECLARE_FIELD(DIVE_SUMMARY, start_cns, UINT8) { } // percent +DECLARE_FIELD(DIVE_SUMMARY, end_cns, UINT8) { } // percent +DECLARE_FIELD(DIVE_SUMMARY, start_n2, UINT16) { } // percent +DECLARE_FIELD(DIVE_SUMMARY, end_n2, UINT16) { } // percent +DECLARE_FIELD(DIVE_SUMMARY, o2_toxicity, UINT16) { } // OTUs +DECLARE_FIELD(DIVE_SUMMARY, dive_number, UINT32) { } +DECLARE_FIELD(DIVE_SUMMARY, bottom_time, UINT32) { garmin->cache.divetime = data / 1000; } // ms struct msg_desc { @@ -625,29 +693,36 @@ static int traverse_definition(struct garmin_parser_t *garmin, } -static int traverse_data(struct garmin_parser_t *garmin) +static dc_status_t +traverse_data(struct garmin_parser_t *garmin) { const unsigned char *data = garmin->base.data; int len = garmin->base.size; unsigned int hdrsize, protocol, profile, datasize; unsigned int time; + // Reset the time and type descriptors before walking + memset(&garmin->sample_data, 0, sizeof(garmin->sample_data)); + memset(garmin->type_desc, 0, sizeof(garmin->type_desc)); + // The data starts with our filename fingerprint. Skip it. + if (len < FIT_NAME_SIZE) + return DC_STATUS_IO; data += FIT_NAME_SIZE; len -= FIT_NAME_SIZE; // The FIT header if (len < 12) - return -1; + return DC_STATUS_IO; hdrsize = data[0]; protocol = data[1]; profile = array_uint16_le(data+2); datasize = array_uint32_le(data+4); if (memcmp(data+8, ".FIT", 4)) - return -1; + return DC_STATUS_IO; if (hdrsize < 12 || datasize > len || datasize + hdrsize + 2 > len) - return -1; + return DC_STATUS_IO; garmin->cache.protocol = protocol; garmin->cache.profile = profile; @@ -679,17 +754,11 @@ static int traverse_data(struct garmin_parser_t *garmin) len = traverse_regular(garmin, data, datasize, record, &time); } if (len <= 0 || len > datasize) - return -1; + return DC_STATUS_IO; data += len; datasize -= len; } - return 0; -} - -static void initialize_field_caches(garmin_parser_t *garmin) -{ - memset(&garmin->cache, 0, sizeof(garmin->cache)); - traverse_data(garmin); + return DC_STATUS_SUCCESS; } static dc_status_t @@ -697,8 +766,13 @@ garmin_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsign { garmin_parser_t *garmin = (garmin_parser_t *) abstract; - memset(garmin->type_desc, 0, sizeof(garmin->type_desc)); - initialize_field_caches(garmin); + /* Walk the data once without a callback to set up the core fields */ + garmin->callback = NULL; + garmin->userdata = NULL; + memset(&garmin->cache, 0, sizeof(garmin->cache)); + + traverse_data(garmin); + flush_pending_sample(garmin); return DC_STATUS_SUCCESS; } @@ -706,22 +780,11 @@ garmin_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsign static dc_status_t garmin_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { - const unsigned char *data = abstract->data; - unsigned int yyyy, mm, dd, h, m, s; + garmin_parser_t *garmin = (garmin_parser_t *) abstract; + dc_ticks_t time = 631065600 + (dc_ticks_t) garmin->cache.time; - if (abstract->size < FIT_NAME_SIZE) - return DC_STATUS_UNSUPPORTED; - - if (sscanf(data, "%04u-%02u-%02u-%02u-%02u-%02u", - &yyyy, &mm, &dd, &h, &m, &s) != 6) - return DC_STATUS_UNSUPPORTED; - - datetime->year = yyyy; - datetime->month = mm; - datetime->day = dd; - datetime->hour = h; - datetime->minute = m; - datetime->second = s; + // Show local time (time_offset) + dc_datetime_gmtime(datetime, time + garmin->cache.time_offset); datetime->timezone = DC_TIMEZONE_NONE; return DC_STATUS_SUCCESS; @@ -731,33 +794,33 @@ garmin_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) static dc_status_t garmin_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) { - const unsigned char *data = abstract->data; + garmin_parser_t *garmin = (garmin_parser_t *) abstract; if (!value) return DC_STATUS_INVALIDARGS; switch (type) { case DC_FIELD_DIVETIME: - *((unsigned int *) value) = 0; + *((unsigned int *) value) = garmin->cache.divetime; break; case DC_FIELD_AVGDEPTH: - *((double *) value) = 0; + *((double *) value) = garmin->cache.avgdepth; break; case DC_FIELD_MAXDEPTH: - *((double *) value) = 0; + *((double *) value) = garmin->cache.maxdepth; break; default: return DC_STATUS_UNSUPPORTED; } - return DC_STATUS_SUCCESS; } static dc_status_t garmin_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { - const unsigned char *data = abstract->data; - unsigned int size = abstract->size; + garmin_parser_t *garmin = (garmin_parser_t *) abstract; - return DC_STATUS_SUCCESS; + garmin->callback = callback; + garmin->userdata = userdata; + return traverse_data(garmin); }