From 6d470d8430245f0df2d1e41875183dfd6b25ce85 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 29 Aug 2018 12:22:42 -0700 Subject: [PATCH] garmin: add a lot of new field definitions This actually seems to cover almost all of the relevant dive fields. There's a lot of different GPS coordinates, I have no idea what they all are, but they are at least marked in the definitions. NOTE! None of this actually fills in any information yet. It's all about just parsing things and getting the types etc right. On that note, this also adds a bit of dynamic type checking, which caught a mistake or two. Signed-off-by: Linus Torvalds --- src/garmin_parser.c | 181 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 159 insertions(+), 22 deletions(-) diff --git a/src/garmin_parser.c b/src/garmin_parser.c index ec6bac5..044645e 100644 --- a/src/garmin_parser.c +++ b/src/garmin_parser.c @@ -54,6 +54,7 @@ typedef struct garmin_parser_t { unsigned int initialized; unsigned int protocol; unsigned int profile; + unsigned int utc_offset, time_offset; unsigned int divetime; double maxdepth; double avgdepth; @@ -128,6 +129,30 @@ DECLARE_FIT_TYPE(SINT16, signed short, 0x7fff); DECLARE_FIT_TYPE(SINT32, signed int, 0x7fffffff); DECLARE_FIT_TYPE(SINT64, signed long long, 0x7fffffffffffffffll); +static const struct { + const char *type_name; + int type_size; + unsigned long long type_inval; +} base_type_info[17] = { + { "ENUM", 1, 0xff }, + { "SINT8", 1, 0x7f }, + { "UINT8", 1, 0xff }, + { "SINT16", 2, 0x7fff }, + { "UINT16", 2, 0xffff }, + { "SINT32", 4, 0x7fffffff }, + { "UINT32", 4, 0xffffffff }, + { "STRING", 1, 0 }, + { "FLOAT", 4, 0xffffffff }, + { "DOUBLE", 8, 0xfffffffffffffffful }, + { "UINT8Z", 1, 0x00 }, + { "UINT16Z", 2, 0x0000 }, + { "UINT32Z", 4, 0x00000000 }, + { "BYTE", 1, 0xff }, + { "SINT64", 8, 0x7fffffffffffffff }, + { "UINT64", 8, 0xffffffffffffffff }, + { "UINT64Z", 8, 0x0000000000000000 }, +}; + /* * Garmin FIT events are described by tuples of "global mesg ID" and * a "field number". There's lots of them, because you have events @@ -139,17 +164,19 @@ DECLARE_FIT_TYPE(SINT64, signed long long, 0x7fffffffffffffffll); */ struct field_desc { const char *name; - int (*parse)(struct garmin_parser_t *, const unsigned char *data); + int (*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, const unsigned char *p) \ + static int 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; \ - fprintf(stderr, "%s: %llx\n", #name, (long long)val); \ + DEBUG(g->base.context, "%s (%s): %lld\n", #name, #type, (long long)val); \ return parse_##name(g, *(type *)p); \ } \ static const struct field_desc name##_field_##type = { #name, parse_##name##_##type }; \ @@ -163,8 +190,10 @@ DECLARE_FIELD(ANY, timestamp, UINT32) dc_ticks_t time = 631065600 + (dc_ticks_t) data; dc_datetime_t date; - dc_datetime_gmtime(&date, time); - fprintf(stderr, "%04d-%02d-%02d %2d:%02d:%02d\n", + // 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; @@ -176,19 +205,69 @@ DECLARE_FIELD(ANY, part_index, UINT32) { return 0; } 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, UINT32) { 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); } // 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 // 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 // RECORD msg -DECLARE_FIELD(RECORD, start_time, UINT32) { return parse_ANY_timestamp(garmin, data); } +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 + +// 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; } + +// 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 + +// 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 struct msg_desc { @@ -208,47 +287,107 @@ DECLARE_MESG(FILE) = { SET_FIELD(FILE, 0, file_type, ENUM), SET_FIELD(FILE, 1, manufacturer, UINT16), SET_FIELD(FILE, 2, product, UINT16), - SET_FIELD(FILE, 3, serial, UINT32), + SET_FIELD(FILE, 3, serial, UINT32Z), SET_FIELD(FILE, 4, creation_time, UINT32), SET_FIELD(FILE, 5, number, UINT16), SET_FIELD(FILE, 7, other_time, UINT32), } }; -DECLARE_MESG(DEVICE_SETTINGS) = { }; +DECLARE_MESG(DEVICE_SETTINGS) = { + .maxfield = 3, + .field = { + SET_FIELD(DEVICE_SETTINGS, 1, utc_offset, UINT32), // Convert to UTC + SET_FIELD(DEVICE_SETTINGS, 2, time_offset, UINT32), // Convert to local + } +}; DECLARE_MESG(USER_PROFILE) = { }; DECLARE_MESG(ZONES_TARGET) = { }; DECLARE_MESG(SPORT) = { }; DECLARE_MESG(SESSION) = { - .maxfield = 3, + .maxfield = 40, .field = { SET_FIELD(SESSION, 2, start_time, UINT32), + SET_FIELD(SESSION, 3, start_pos_lat, SINT32), // 180 deg / 2**31 + SET_FIELD(SESSION, 4, start_pos_long, SINT32), // 180 deg / 2**31 + SET_FIELD(SESSION, 29, nec_pos_lat, SINT32), // 180 deg / 2**31 + SET_FIELD(SESSION, 30, nec_pos_long, SINT32), // 180 deg / 2**31 + SET_FIELD(SESSION, 31, swc_pos_lat, SINT32), // 180 deg / 2**31 + SET_FIELD(SESSION, 32, swc_pos_long, SINT32), // 180 deg / 2**31 + SET_FIELD(SESSION, 38, exit_pos_lat, SINT32), // 180 deg / 2**31 + SET_FIELD(SESSION, 39, exit_pos_long, SINT32), // 180 deg / 2**31 } }; DECLARE_MESG(LAP) = { - .maxfield = 3, + .maxfield = 31, .field = { SET_FIELD(LAP, 2, start_time, UINT32), + SET_FIELD(LAP, 3, start_pos_lat, SINT32), // 180 deg / 2**31 + SET_FIELD(LAP, 4, start_pos_long, SINT32), // 180 deg / 2**31 + SET_FIELD(LAP, 5, end_pos_lat, SINT32), // 180 deg / 2**31 + SET_FIELD(LAP, 6, end_pos_long, SINT32), // 180 deg / 2**31 + SET_FIELD(LAP, 27, some_pos_lat, SINT32), // 180 deg / 2**31 + SET_FIELD(LAP, 28, some_pos_long, SINT32), // 180 deg / 2**31 + SET_FIELD(LAP, 29, other_pos_lat, SINT32), // 180 deg / 2**31 + SET_FIELD(LAP, 30, other_pos_long, SINT32), // 180 deg / 2**31 } }; DECLARE_MESG(RECORD) = { - .maxfield = 3, + .maxfield = 99, .field = { - SET_FIELD(RECORD, 2, start_time, UINT32), + SET_FIELD(RECORD, 0, position_lat, SINT32), // 180 deg / 2**31 + SET_FIELD(RECORD, 1, position_long, SINT32), // 180 deg / 2**31 + SET_FIELD(RECORD, 2, altitude, UINT16), // 5 *m + 500 ? + SET_FIELD(RECORD, 3, heart_rate, UINT8), // bpm + SET_FIELD(RECORD, 5, distance, UINT32), // Distance in 100 * m? WTF? + SET_FIELD(RECORD, 13, temperature, SINT8), // degrees C + SET_FIELD(RECORD, 91, abs_pressure, UINT32), // Pascal + SET_FIELD(RECORD, 92, depth, UINT32), // mm + SET_FIELD(RECORD, 93, next_stop_depth, UINT32), // mm + SET_FIELD(RECORD, 94, next_stop_time, UINT32), // seconds + SET_FIELD(RECORD, 95, tts, UINT32), // seconds + SET_FIELD(RECORD, 96, ndl, UINT32), // s + SET_FIELD(RECORD, 97, cns_load, UINT8), // percent + SET_FIELD(RECORD, 98, n2_load, UINT16), // percent } }; +DECLARE_MESG(DIVE_GAS) = { + .maxfield = 3, + .field = { + // This uses a "message index" field to set the gas index + SET_FIELD(DIVE_GAS, 0, helium, UINT8), + SET_FIELD(DIVE_GAS, 1, oxygen, UINT8), + SET_FIELD(DIVE_GAS, 2, status, ENUM), + } +}; + +DECLARE_MESG(DIVE_SUMMARY) = { + .maxfield = 12, + .field = { + SET_FIELD(DIVE_SUMMARY, 2, avg_depth, UINT32), // mm + SET_FIELD(DIVE_SUMMARY, 3, max_depth, UINT32), // mm + SET_FIELD(DIVE_SUMMARY, 4, surface_interval, UINT32), // sec + SET_FIELD(DIVE_SUMMARY, 5, start_cns, UINT8), // percent + SET_FIELD(DIVE_SUMMARY, 6, end_cns, UINT8), // percent + SET_FIELD(DIVE_SUMMARY, 7, start_n2, UINT16), // percent + SET_FIELD(DIVE_SUMMARY, 8, end_n2, UINT16), // percent + SET_FIELD(DIVE_SUMMARY, 9, o2_toxicity, UINT16), // OTUs + SET_FIELD(DIVE_SUMMARY, 10, dive_number, UINT32), + SET_FIELD(DIVE_SUMMARY, 11, bottom_time, UINT32), // ms + } +}; + + DECLARE_MESG(EVENT) = { }; DECLARE_MESG(DEVICE_INFO) = { }; DECLARE_MESG(ACTIVITY) = { }; DECLARE_MESG(FILE_CREATOR) = { }; DECLARE_MESG(DIVE_SETTINGS) = { }; -DECLARE_MESG(DIVE_GAS) = { }; DECLARE_MESG(DIVE_ALARM) = { }; -DECLARE_MESG(DIVE_SUMMARY) = { }; // Unknown global message ID's.. DECLARE_MESG(WTF_13) = { }; @@ -258,6 +397,7 @@ DECLARE_MESG(WTF_104) = { }; DECLARE_MESG(WTF_125) = { }; DECLARE_MESG(WTF_140) = { }; DECLARE_MESG(WTF_141) = { }; +DECLARE_MESG(WTF_216) = { }; DECLARE_MESG(WTF_233) = { }; #define SET_MESG(nr, name) [nr] = { #name, &name##_msg_desc } @@ -287,6 +427,7 @@ static const struct { SET_MESG(140, WTF_140), SET_MESG(141, WTF_141), + SET_MESG(216, WTF_216), SET_MESG(233, WTF_233), SET_MESG(258, DIVE_SETTINGS), SET_MESG(259, DIVE_GAS), @@ -404,14 +545,10 @@ static int traverse_regular(struct garmin_parser_t *garmin, } if (field_desc) { - field_desc->parse(garmin, data); + field_desc->parse(garmin, base_type, data); } else { #if 1 - static const unsigned long long inval_value_array[] = { - 0xff, 0x7f, 0xff, 0x7fff, 0xffff, 0x7fffffff, 0xffffffff, 0, - 0xffffffff, 0xfffffffffffffffful, 0x00, 0x0000, 0x00000000, 0xff, - 0x7fffffffffffffff, 0xffffffffffffffff, 0x0000000000000000 }; - const unsigned long long inval = inval_value_array[base_type]; + const unsigned long long inval = base_type_info[base_type].type_inval; switch (base_type) { case 7: @@ -433,7 +570,7 @@ static int traverse_regular(struct garmin_parser_t *garmin, } fprintf(stderr, fmt, val); } - fprintf(stderr, "\n"); + fprintf(stderr, " %s\n", base_type_info[base_type].type_name); } #else DEBUG(garmin->base.context, "Unknown field %s:%02x %02x %d/%d\n", msg_name, field_nr, field[2], len, base_size);