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 <torvalds@linux-foundation.org>
This commit is contained in:
Linus Torvalds 2018-08-29 15:55:48 -07:00
parent 00a90e2822
commit c8e52081cd

View File

@ -48,13 +48,26 @@ struct type_desc {
typedef struct garmin_parser_t { typedef struct garmin_parser_t {
dc_parser_t base; 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]; struct type_desc type_desc[MAXTYPE];
// Field cache // Field cache
struct { struct {
unsigned int initialized; unsigned int initialized;
unsigned int protocol; unsigned int protocol;
unsigned int profile; unsigned int profile;
unsigned int utc_offset, time_offset; unsigned int time;
int utc_offset, time_offset;
unsigned int divetime; unsigned int divetime;
double maxdepth; double maxdepth;
double avgdepth; 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); 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_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_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); 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 { struct field_desc {
const char *name; 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(msg, name, type) __DECLARE_FIELD(msg##_##name, type)
#define __DECLARE_FIELD(name, type) \ #define __DECLARE_FIELD(name, type) \
static int parse_##name(struct garmin_parser_t *, const type); \ static void 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##_##type(struct garmin_parser_t *g, unsigned char base_type, const unsigned char *p) \
{ \ { \
if (strcmp(#type, base_type_info[base_type].type_name)) \ 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); \ fprintf(stderr, "%s: %s should be %s\n", #name, #type, base_type_info[base_type].type_name); \
type val = *(type *)p; \ 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); \ 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 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 // All msg formats can have a timestamp
// Garmin timestamps are in seconds since 00:00 Dec 31 1989 UTC // Garmin timestamps are in seconds since 00:00 Dec 31 1989 UTC
// Convert to "standard epoch time" by adding 631065600. // Convert to "standard epoch time" by adding 631065600.
DECLARE_FIELD(ANY, timestamp, UINT32) DECLARE_FIELD(ANY, timestamp, UINT32)
{ {
dc_ticks_t time = 631065600 + (dc_ticks_t) data; if (garmin->callback) {
dc_datetime_t date; dc_sample_value_t sample = {0};
// Show local time (time_offset) // Turn the timestamp relative to the beginning of the dive
dc_datetime_gmtime(&date, time + garmin->cache.time_offset); if (data < garmin->cache.time)
DEBUG(garmin->base.context, return;
"%04d-%02d-%02d %02d:%02d:%02d", data -= garmin->cache.time;
date.year, date.month, date.day,
date.hour, date.minute, date.second); // Did we already do this?
return 0; 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, message_index, UINT16) { }
DECLARE_FIELD(ANY, part_index, UINT32) { return 0; } DECLARE_FIELD(ANY, part_index, UINT32) { }
// FILE msg // FILE msg
DECLARE_FIELD(FILE, file_type, ENUM) { return 0; } DECLARE_FIELD(FILE, file_type, ENUM) { }
DECLARE_FIELD(FILE, manufacturer, UINT16) { return 0; } DECLARE_FIELD(FILE, manufacturer, UINT16) { }
DECLARE_FIELD(FILE, product, UINT16) { return 0; } DECLARE_FIELD(FILE, product, UINT16) { }
DECLARE_FIELD(FILE, serial, UINT32Z) { return 0; } DECLARE_FIELD(FILE, serial, UINT32Z) { }
DECLARE_FIELD(FILE, creation_time, UINT32) { return parse_ANY_timestamp(garmin, data); } DECLARE_FIELD(FILE, creation_time, UINT32) { }
DECLARE_FIELD(FILE, number, UINT16) { return 0; } DECLARE_FIELD(FILE, number, UINT16) { }
DECLARE_FIELD(FILE, other_time, UINT32) { return parse_ANY_timestamp(garmin, data); } DECLARE_FIELD(FILE, other_time, UINT32) { }
// SESSION msg // SESSION msg
DECLARE_FIELD(SESSION, start_time, UINT32) { return parse_ANY_timestamp(garmin, data); } DECLARE_FIELD(SESSION, start_time, UINT32) { garmin->cache.time = data; }
DECLARE_FIELD(SESSION, start_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(SESSION, start_pos_lat, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(SESSION, start_pos_long, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(SESSION, start_pos_long, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(SESSION, nec_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(SESSION, nec_pos_lat, SINT32) { } // 180 deg / 2**31 NE corner
DECLARE_FIELD(SESSION, nec_pos_long, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(SESSION, nec_pos_long, SINT32) { } // 180 deg / 2**31 pos
DECLARE_FIELD(SESSION, swc_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(SESSION, swc_pos_lat, SINT32) { } // 180 deg / 2**31 SW corner
DECLARE_FIELD(SESSION, swc_pos_long, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(SESSION, swc_pos_long, SINT32) { } // 180 deg / 2**31 pos
DECLARE_FIELD(SESSION, exit_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(SESSION, exit_pos_lat, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(SESSION, exit_pos_long, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(SESSION, exit_pos_long, SINT32) { } // 180 deg / 2**31
// LAP msg // LAP msg
DECLARE_FIELD(LAP, start_time, UINT32) { return parse_ANY_timestamp(garmin, data); } DECLARE_FIELD(LAP, start_time, UINT32) { }
DECLARE_FIELD(LAP, start_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(LAP, start_pos_lat, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(LAP, start_pos_long, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(LAP, start_pos_long, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(LAP, end_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(LAP, end_pos_lat, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(LAP, end_pos_long, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(LAP, end_pos_long, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(LAP, some_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(LAP, some_pos_lat, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(LAP, some_pos_long, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(LAP, some_pos_long, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(LAP, other_pos_lat, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(LAP, other_pos_lat, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(LAP, other_pos_long, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(LAP, other_pos_long, SINT32) { } // 180 deg / 2**31
// RECORD msg // RECORD msg
DECLARE_FIELD(RECORD, position_lat, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(RECORD, position_lat, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(RECORD, position_long, SINT32) { return 0; } // 180 deg / 2**31 DECLARE_FIELD(RECORD, position_long, SINT32) { } // 180 deg / 2**31
DECLARE_FIELD(RECORD, altitude, UINT16) { return 0; } // 5 *m + 500 ? DECLARE_FIELD(RECORD, altitude, UINT16) { } // 5 *m + 500 ?
DECLARE_FIELD(RECORD, heart_rate, UINT8) { return 0; } // bpm DECLARE_FIELD(RECORD, heart_rate, UINT8) { } // bpm
DECLARE_FIELD(RECORD, distance, UINT32) { return 0; } // Distance in 100 * m? WTF? DECLARE_FIELD(RECORD, distance, UINT32) { } // Distance in 100 * m? WTF?
DECLARE_FIELD(RECORD, temperature, SINT8) { return 0; } // degrees C DECLARE_FIELD(RECORD, temperature, SINT8) // degrees C
DECLARE_FIELD(RECORD, abs_pressure, UINT32) {return 0; } // Pascal {
DECLARE_FIELD(RECORD, depth, UINT32) { return 0; } // mm if (garmin->callback) {
DECLARE_FIELD(RECORD, next_stop_depth, UINT32) { return 0; } // mm dc_sample_value_t sample = {0};
DECLARE_FIELD(RECORD, next_stop_time, UINT32) { return 0; } // seconds sample.temperature = data;
DECLARE_FIELD(RECORD, tts, UINT32) { return 0; } // seconds garmin->callback(DC_SAMPLE_TEMPERATURE, sample, garmin->userdata);
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, 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 // DEVICE_SETTINGS
DECLARE_FIELD(DEVICE_SETTINGS, utc_offset, UINT32) { garmin->cache.utc_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 = data; return 0; } DECLARE_FIELD(DEVICE_SETTINGS, time_offset, UINT32) { garmin->cache.time_offset = (SINT32) data; } // wrong type in FIT
// DIVE_GAS - uses msg index // DIVE_GAS - uses msg index
DECLARE_FIELD(DIVE_GAS, helium, UINT8) { return 0; } // percent DECLARE_FIELD(DIVE_GAS, helium, UINT8) { } // percent
DECLARE_FIELD(DIVE_GAS, oxygen, UINT8) { return 0; } // percent DECLARE_FIELD(DIVE_GAS, oxygen, UINT8) { } // percent
DECLARE_FIELD(DIVE_GAS, status, ENUM) { return 0; } // 0 - disabled, 1 - enabled, 2 - backup DECLARE_FIELD(DIVE_GAS, status, ENUM) { } // 0 - disabled, 1 - enabled, 2 - backup
// DIVE_SUMMARY // DIVE_SUMMARY
DECLARE_FIELD(DIVE_SUMMARY, avg_depth, UINT32) { return 0; } // mm DECLARE_FIELD(DIVE_SUMMARY, avg_depth, UINT32) { garmin->cache.avgdepth = data / 1000.0; } // mm
DECLARE_FIELD(DIVE_SUMMARY, max_depth, UINT32) { return 0; } // mm DECLARE_FIELD(DIVE_SUMMARY, max_depth, UINT32) { garmin->cache.maxdepth = data / 1000.0; } // mm
DECLARE_FIELD(DIVE_SUMMARY, surface_interval, UINT32) { return 0; } // sec DECLARE_FIELD(DIVE_SUMMARY, surface_interval, UINT32) { } // sec
DECLARE_FIELD(DIVE_SUMMARY, start_cns, UINT8) { return 0; } // percent DECLARE_FIELD(DIVE_SUMMARY, start_cns, UINT8) { } // percent
DECLARE_FIELD(DIVE_SUMMARY, end_cns, UINT8) { return 0; } // percent DECLARE_FIELD(DIVE_SUMMARY, end_cns, UINT8) { } // percent
DECLARE_FIELD(DIVE_SUMMARY, start_n2, UINT16) { return 0; } // percent DECLARE_FIELD(DIVE_SUMMARY, start_n2, UINT16) { } // percent
DECLARE_FIELD(DIVE_SUMMARY, end_n2, UINT16) { return 0; } // percent DECLARE_FIELD(DIVE_SUMMARY, end_n2, UINT16) { } // percent
DECLARE_FIELD(DIVE_SUMMARY, o2_toxicity, UINT16) { return 0; } // OTUs DECLARE_FIELD(DIVE_SUMMARY, o2_toxicity, UINT16) { } // OTUs
DECLARE_FIELD(DIVE_SUMMARY, dive_number, UINT32) { return 0; } DECLARE_FIELD(DIVE_SUMMARY, dive_number, UINT32) { }
DECLARE_FIELD(DIVE_SUMMARY, bottom_time, UINT32) { return 0; } // ms DECLARE_FIELD(DIVE_SUMMARY, bottom_time, UINT32) { garmin->cache.divetime = data / 1000; } // ms
struct msg_desc { 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; const unsigned char *data = garmin->base.data;
int len = garmin->base.size; int len = garmin->base.size;
unsigned int hdrsize, protocol, profile, datasize; unsigned int hdrsize, protocol, profile, datasize;
unsigned int time; 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. // The data starts with our filename fingerprint. Skip it.
if (len < FIT_NAME_SIZE)
return DC_STATUS_IO;
data += FIT_NAME_SIZE; data += FIT_NAME_SIZE;
len -= FIT_NAME_SIZE; len -= FIT_NAME_SIZE;
// The FIT header // The FIT header
if (len < 12) if (len < 12)
return -1; return DC_STATUS_IO;
hdrsize = data[0]; hdrsize = data[0];
protocol = data[1]; protocol = data[1];
profile = array_uint16_le(data+2); profile = array_uint16_le(data+2);
datasize = array_uint32_le(data+4); datasize = array_uint32_le(data+4);
if (memcmp(data+8, ".FIT", 4)) if (memcmp(data+8, ".FIT", 4))
return -1; return DC_STATUS_IO;
if (hdrsize < 12 || datasize > len || datasize + hdrsize + 2 > len) if (hdrsize < 12 || datasize > len || datasize + hdrsize + 2 > len)
return -1; return DC_STATUS_IO;
garmin->cache.protocol = protocol; garmin->cache.protocol = protocol;
garmin->cache.profile = profile; 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); len = traverse_regular(garmin, data, datasize, record, &time);
} }
if (len <= 0 || len > datasize) if (len <= 0 || len > datasize)
return -1; return DC_STATUS_IO;
data += len; data += len;
datasize -= len; datasize -= len;
} }
return 0; return DC_STATUS_SUCCESS;
}
static void initialize_field_caches(garmin_parser_t *garmin)
{
memset(&garmin->cache, 0, sizeof(garmin->cache));
traverse_data(garmin);
} }
static dc_status_t 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; garmin_parser_t *garmin = (garmin_parser_t *) abstract;
memset(garmin->type_desc, 0, sizeof(garmin->type_desc)); /* Walk the data once without a callback to set up the core fields */
initialize_field_caches(garmin); garmin->callback = NULL;
garmin->userdata = NULL;
memset(&garmin->cache, 0, sizeof(garmin->cache));
traverse_data(garmin);
flush_pending_sample(garmin);
return DC_STATUS_SUCCESS; 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 static dc_status_t
garmin_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) garmin_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
{ {
const unsigned char *data = abstract->data; garmin_parser_t *garmin = (garmin_parser_t *) abstract;
unsigned int yyyy, mm, dd, h, m, s; dc_ticks_t time = 631065600 + (dc_ticks_t) garmin->cache.time;
if (abstract->size < FIT_NAME_SIZE) // Show local time (time_offset)
return DC_STATUS_UNSUPPORTED; dc_datetime_gmtime(datetime, time + garmin->cache.time_offset);
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;
datetime->timezone = DC_TIMEZONE_NONE; datetime->timezone = DC_TIMEZONE_NONE;
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
@ -731,33 +794,33 @@ garmin_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
static dc_status_t static dc_status_t
garmin_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) 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) if (!value)
return DC_STATUS_INVALIDARGS; return DC_STATUS_INVALIDARGS;
switch (type) { switch (type) {
case DC_FIELD_DIVETIME: case DC_FIELD_DIVETIME:
*((unsigned int *) value) = 0; *((unsigned int *) value) = garmin->cache.divetime;
break; break;
case DC_FIELD_AVGDEPTH: case DC_FIELD_AVGDEPTH:
*((double *) value) = 0; *((double *) value) = garmin->cache.avgdepth;
break; break;
case DC_FIELD_MAXDEPTH: case DC_FIELD_MAXDEPTH:
*((double *) value) = 0; *((double *) value) = garmin->cache.maxdepth;
break; break;
default: default:
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
static dc_status_t static dc_status_t
garmin_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) garmin_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{ {
const unsigned char *data = abstract->data; garmin_parser_t *garmin = (garmin_parser_t *) abstract;
unsigned int size = abstract->size;
return DC_STATUS_SUCCESS; garmin->callback = callback;
garmin->userdata = userdata;
return traverse_data(garmin);
} }