Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ static const backend_table_t g_backends[] = {
{"symbios", DC_FAMILY_HALCYON_SYMBIOS, 1},

// Not merged upstream yet
{"descentmk1", DC_FAMILY_GARMIN, 0},
{"descentmk1", DC_FAMILY_GARMIN, 2859},
};

static const transport_table_t g_transports[] = {
Expand Down
4 changes: 4 additions & 0 deletions include/libdivecomputer/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,10 @@ dc_parser_set_density (dc_parser_t *parser, double density);
dc_status_t
dc_parser_get_datetime (dc_parser_t *parser, dc_datetime_t *datetime);

/* Returns parser-provided device information when available. */
dc_status_t
dc_parser_get_device_info (dc_parser_t *parser, dc_event_devinfo_t *devinfo);

/* DC_FIELD_STRING values are caller-owned and must be freed after use. */
dc_status_t
dc_parser_get_field (dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value);
Expand Down
2 changes: 1 addition & 1 deletion src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ libdivecomputer_la_SOURCES = \
libdivecomputer_la_SOURCES += \
usb_storage.c \
field-cache.h field-cache.c \
garmin.h garmin.c garmin_parser.c
garmin.h garmin-models.h garmin.c garmin_parser.c

if OS_WIN32
libdivecomputer_la_SOURCES += serial_win32.c
Expand Down
13 changes: 6 additions & 7 deletions src/descriptor.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "iterator-private.h"
#include "platform.h"
#include "array.h"
#include "garmin-models.h"

#define DC_FILTER_INTERNAL(key, values, isnullterminated, match) \
dc_filter_internal( \
Expand Down Expand Up @@ -546,13 +547,11 @@ static const dc_descriptor_t g_descriptors[] = {
{"Halcyon", "Symbios HUD", DC_FAMILY_HALCYON_SYMBIOS, 1, DC_TRANSPORT_BLE, dc_filter_halcyon},
{"Halcyon", "Symbios Handset", DC_FAMILY_HALCYON_SYMBIOS, 7, DC_TRANSPORT_BLE, dc_filter_halcyon},

// Not merged upstream yet
/* Garmin -- model numbers as defined in FIT format; USB product id is (0x4000 | model) */
/* for the Mk1 we are using the model of the global model */
/* for the Mk2/Mk3 we are using the model of the Mk2 global model */
/* see garmin_parser.c for a more comprehensive list of models */
{"Garmin", "Descent Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, NULL},
{"Garmin", "Descent Mk2(i)/Mk3(i)(S)/G1/G2/X50i", DC_FAMILY_GARMIN, 3258, DC_TRANSPORT_USBSTORAGE, NULL},
/* Garmin */
#define GARMIN_DESCRIPTOR(name, id, mtp_capable) {"Garmin", name, DC_FAMILY_GARMIN, id, DC_TRANSPORT_USBSTORAGE, NULL},
GARMIN_MODEL_LIST(GARMIN_DESCRIPTOR)
#undef GARMIN_DESCRIPTOR
/* Generic FIT file import, not a hardware model. */
{"FIT", "File import", DC_FAMILY_GARMIN, 0, DC_TRANSPORT_USBSTORAGE, NULL },
};

Expand Down
57 changes: 57 additions & 0 deletions src/garmin-models.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// AI-generated (Claude)
/*
* libdivecomputer
*
* Copyright (C) 2026 Michael Keller
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/

#ifndef GARMIN_MODELS_H
#define GARMIN_MODELS_H

#include <stdbool.h>

typedef struct {
const char *name;
int id;
bool mtp_capable;
} garmin_model_t;

/* FIT product IDs. See the Garmin Connect IQ device reference. */
#define GARMIN_MODEL_LIST(MODEL) \
MODEL("Descent™ G1 / G1 Solar", 4005, true) \
MODEL("Descent™ G2", 4588, true) \
MODEL("Descent™ Mk1", 2859, false) \
MODEL("Descent™ Mk1 APAC", 2991, false) \
MODEL("Descent™ Mk2(i)", 3258, true) \
MODEL("Descent™ Mk2(i) APAC", 3702, true) \
MODEL("Descent™ Mk2 S", 3542, true) \
MODEL("Descent™ Mk2 S APAC", 3930, true) \
MODEL("Descent™ Mk3(i) 43mm", 4222, true) \
MODEL("Descent™ Mk3(i) 51mm", 4223, true) \
MODEL("Descent™ X50i", 4518, true) \
MODEL("fēnix® 8 43mm", 4534, true) \
MODEL("fēnix® 8 47mm / 51mm / tactix® 8 47mm / 51mm / quatix® 8 47mm / 51mm APAC", 4536, true) \
MODEL("fēnix® 8 47mm / 51mm / tactix® 8 47mm / 51mm / quatix® 8 47mm / 51mm", 4775, true) \
MODEL("fēnix® 8 Pro 47mm / 51mm / MicroLED / quatix® 8 Pro 47mm / 51mm", 4631, true) \
MODEL("fēnix® 8 Solar 47mm", 4532, true) \
MODEL("fēnix® 8 Solar 51mm / tactix® 8 Solar 51mm APAC", 4533, true) \
MODEL("fēnix® 8 Solar 51mm / tactix® 8 Solar 51mm", 4776, true)

extern const garmin_model_t garmin_models[];

#endif /* GARMIN_MODELS_H */
68 changes: 30 additions & 38 deletions src/garmin.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

#include "platform.h"
#include "garmin.h"
#include "garmin-models.h"
#include "context-private.h"
#include "device-private.h"
#include "array.h"
Expand All @@ -65,8 +66,6 @@

#define GARMIN_VENDOR 0x091E

#define DESCENT_MK2 3258

// deal with ancient libmpt found on older Linux distros
#ifndef LIBMTP_FILES_AND_FOLDERS_ROOT
#define LIBMTP_FILES_AND_FOLDERS_ROOT 0xffffffff
Expand All @@ -84,30 +83,33 @@ typedef struct garmin_device_t {
#endif
} garmin_device_t;

// Ids can be found at https://developer.garmin.com/connect-iq/device-reference/
// (look for 'Part Number')

#define GARMIN_MODEL(name, id, mtp_capable) {name, id, mtp_capable},
const garmin_model_t garmin_models[] = {
{ "Descent™ G1 / G1 Solar", 4005, true },
{ "Descent™ G2", 4588, true },
{ "Descent™ Mk1", 2859, false },
{ "Descent™ Mk1 APAC", 2991, false },
{ "Descent™ Mk2(i)", 3258, true },
{ "Descent™ Mk2(i) APAC", 3702, true },
{ "Descent™ Mk2 S", 3542, true },
{ "Descent™ Mk2 S APAC", 3930, true },
{ "Descent™ Mk3(i) 43mm", 4222, true },
{ "Descent™ Mk3(i) 51mm", 4223, true },
{ "Descent™ X50i", 4518, true },
{ "fēnix® 8 43mm", 4534, true },
{ "fēnix® 8 47mm / 51mm / tactix® 8 47mm / 51mm / quatix® 8 47mm / 51mm APAC", 4536, true },
{ "fēnix® 8 47mm / 51mm / tactix® 8 47mm / 51mm / quatix® 8 47mm / 51mm", 4775, true },
{ "fēnix® 8 Pro 47mm / 51mm / MicroLED / quatix® 8 Pro 47mm / 51mm", 4631, true },
{ "fēnix® 8 Solar 47mm", 4532, true },
{ "fēnix® 8 Solar 51mm / tactix® 8 Solar 51mm APAC", 4533, true },
{ "fēnix® 8 Solar 51mm / tactix® 8 Solar 51mm", 4776, true },
GARMIN_MODEL_LIST(GARMIN_MODEL)
{ NULL, 0, false }
};
#undef GARMIN_MODEL

// AI-generated (Claude)
#ifdef HAVE_LIBMTP
static const garmin_model_t *garmin_model_find(unsigned int id)
{
unsigned int i;

for (i = 0; garmin_models[i].name; i++)
if (garmin_models[i].id == id)
return &garmin_models[i];

return NULL;
}

static bool garmin_model_is_mtp_capable(unsigned int id)
{
const garmin_model_t *model = garmin_model_find(id);

return model && model->mtp_capable;
}
#endif

static dc_status_t garmin_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
static dc_status_t garmin_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
Expand Down Expand Up @@ -146,10 +148,8 @@ garmin_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *ios
device->model = model;

#ifdef HAVE_LIBMTP
// for a Descent Mk2/Mk2i, we have to use MTP to access its storage;
// for Garmin devices, the model number corresponds to the lower three nibbles of the USB product ID
// in order to have only one entry for the Mk2, we don't use the Mk2/APAC model number in our code
device->use_mtp = model == DESCENT_MK2;
// Garmin USB product IDs are 0x4000 | FIT model IDs.
device->use_mtp = garmin_model_is_mtp_capable(model);
device->mtp_device = NULL;
#endif

Expand Down Expand Up @@ -428,23 +428,15 @@ mtp_get_file_list(dc_device_t *abstract, struct file_list *files)
/* iterate through connected MTP devices */
for (i = 0; i < numrawdevices; i++) {
LIBMTP_devicestorage_t *storage;
// we only want to read from a Garmin Descent Mk2 device at this point
// We only read supported Garmin devices at this point.
if (rawdevices[i].device_entry.vendor_id != GARMIN_VENDOR) {
DEBUG(abstract->context, "Garmin/mtp: skipping raw device %04x/%04x",
rawdevices[i].device_entry.vendor_id, rawdevices[i].device_entry.product_id);
continue;
}

bool mtp_capable = false;
for (unsigned j = 0; garmin_models[j].name; j++) {
if ((garmin_models[j].id | 0x4000) == rawdevices[i].device_entry.product_id) {
mtp_capable = garmin_models[j].mtp_capable;

break;
}
}

if (!mtp_capable) {
if (!(rawdevices[i].device_entry.product_id & 0x4000u) ||
!garmin_model_is_mtp_capable(rawdevices[i].device_entry.product_id & ~0x4000u)) {
DEBUG(abstract->context, "Garmin/mtp: skipping Garmin raw device %04x/%04x, as it is not a dive computer / does not support MTP",
rawdevices[i].device_entry.vendor_id, rawdevices[i].device_entry.product_id);
continue;
Expand Down
13 changes: 3 additions & 10 deletions src/garmin.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
#ifndef GARMIN_H
#define GARMIN_H

#include <stdbool.h>

#include <libdivecomputer/context.h>
#include <libdivecomputer/iostream.h>
#include <libdivecomputer/device.h>
Expand All @@ -33,20 +31,15 @@
extern "C" {
#endif /* __cplusplus */

typedef struct {
const char *name;
int id;
bool mtp_capable;
} garmin_model_t;

extern const garmin_model_t garmin_models[];

dc_status_t
garmin_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model);

dc_status_t
garmin_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size);

dc_status_t
garmin_parser_get_device_info (dc_parser_t *abstract, dc_event_devinfo_t *devinfo);

// we need to be able to call into the parser to check if the
// files that we find are actual dives
int
Expand Down
14 changes: 14 additions & 0 deletions src/garmin_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <string.h>

#include "garmin.h"
#include "garmin-models.h"
#include "context-private.h"
#include "parser-private.h"
#include "array.h"
Expand Down Expand Up @@ -1662,6 +1663,19 @@ garmin_parser_is_dive (dc_parser_t *abstract, dc_event_devinfo_t *devinfo_p)
}
}

// AI-generated (Claude)
dc_status_t
garmin_parser_get_device_info (dc_parser_t *abstract, dc_event_devinfo_t *devinfo)
{
garmin_parser_t *garmin = (garmin_parser_t *) abstract;

devinfo->firmware = garmin->dive.firmware;
devinfo->serial = garmin->dive.serial;
devinfo->model = garmin->dive.product;

return DC_STATUS_SUCCESS;
}

static void add_sensor_string(garmin_parser_t *garmin, const char *desc, const struct garmin_sensor *sensor)
{
dc_field_add_string_fmt(&garmin->cache, desc, "%x", sensor->sensor_id);
Expand Down
1 change: 1 addition & 0 deletions src/libdivecomputer.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ dc_parser_set_atmospheric
dc_parser_set_density
dc_parser_get_type
dc_parser_get_datetime
dc_parser_get_device_info
dc_parser_get_field
dc_parser_samples_foreach
dc_parser_destroy
Expand Down
16 changes: 16 additions & 0 deletions src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,22 @@ dc_parser_get_datetime (dc_parser_t *parser, dc_datetime_t *datetime)
return parser->vtable->datetime (parser, datetime);
}

dc_status_t
dc_parser_get_device_info (dc_parser_t *parser, dc_event_devinfo_t *devinfo)
{
if (parser == NULL || devinfo == NULL)
return DC_STATUS_INVALIDARGS;

memset (devinfo, 0, sizeof (*devinfo));

switch (parser->vtable->type) {
case DC_FAMILY_GARMIN:
return garmin_parser_get_device_info (parser, devinfo);
default:
return DC_STATUS_UNSUPPORTED;
}
}

dc_status_t
dc_parser_get_field (dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value)
{
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
# the library licence. No GPS or personal location data is present.
shearwater_petrel2-0001.bin|Shearwater|Petrel 2
hw_ostc5-0001.bin|Heinrichs Weikamp|OSTC 5
garmin_descent_mk1-0001.bin|Garmin|Descent Mk1
garmin_descent_mk1-0001.bin|Garmin|Descent Mk1