From 861f7507860443b172857831fb1815c77c3c2c7c Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 13 Sep 2026 17:11:19 +1200 Subject: [PATCH 1/4] Garmin: expose FIT product models Register each supported FIT product ID as an individual descriptor, and select MTP from the existing model capability table rather than a Mk2-specific comparison. Add a parser device-info query so FIT imports can select the detected descriptor before parsing. Signed-off-by: Michael Keller --- include/libdivecomputer/parser.h | 4 ++++ src/descriptor.c | 25 ++++++++++++++----- src/garmin.c | 41 +++++++++++++++++++------------- src/libdivecomputer.symbols | 1 + src/parser.c | 14 +++++++++++ test/fixtures/manifest.txt | 2 +- 6 files changed, 63 insertions(+), 24 deletions(-) diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h index 81519277..5c36ab96 100644 --- a/include/libdivecomputer/parser.h +++ b/include/libdivecomputer/parser.h @@ -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); diff --git a/src/descriptor.c b/src/descriptor.c index 5ff31fa8..919b195c 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -547,12 +547,25 @@ static const dc_descriptor_t g_descriptors[] = { {"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 -- model numbers as defined in FIT format; USB product id is (0x4000 | model). */ + {"Garmin", "Descent™ G1 / G1 Solar", DC_FAMILY_GARMIN, 4005, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "Descent™ G2", DC_FAMILY_GARMIN, 4588, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "Descent™ Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "Descent™ Mk1 APAC", DC_FAMILY_GARMIN, 2991, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "Descent™ Mk2(i)", DC_FAMILY_GARMIN, 3258, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "Descent™ Mk2(i) APAC", DC_FAMILY_GARMIN, 3702, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "Descent™ Mk2 S", DC_FAMILY_GARMIN, 3542, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "Descent™ Mk2 S APAC", DC_FAMILY_GARMIN, 3930, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "Descent™ Mk3(i) 43mm", DC_FAMILY_GARMIN, 4222, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "Descent™ Mk3(i) 51mm", DC_FAMILY_GARMIN, 4223, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "Descent™ X50i", DC_FAMILY_GARMIN, 4518, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "fēnix® 8 43mm", DC_FAMILY_GARMIN, 4534, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "fēnix® 8 47mm / 51mm / tactix® 8 47mm / 51mm / quatix® 8 47mm / 51mm APAC", DC_FAMILY_GARMIN, 4536, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "fēnix® 8 47mm / 51mm / tactix® 8 47mm / 51mm / quatix® 8 47mm / 51mm", DC_FAMILY_GARMIN, 4775, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "fēnix® 8 Pro 47mm / 51mm / MicroLED / quatix® 8 Pro 47mm / 51mm", DC_FAMILY_GARMIN, 4631, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "fēnix® 8 Solar 47mm", DC_FAMILY_GARMIN, 4532, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "fēnix® 8 Solar 51mm / tactix® 8 Solar 51mm APAC", DC_FAMILY_GARMIN, 4533, DC_TRANSPORT_USBSTORAGE, NULL}, + {"Garmin", "fēnix® 8 Solar 51mm / tactix® 8 Solar 51mm", DC_FAMILY_GARMIN, 4776, DC_TRANSPORT_USBSTORAGE, NULL}, {"FIT", "File import", DC_FAMILY_GARMIN, 0, DC_TRANSPORT_USBSTORAGE, NULL }, }; diff --git a/src/garmin.c b/src/garmin.c index 22482685..fbecf4ef 100644 --- a/src/garmin.c +++ b/src/garmin.c @@ -65,8 +65,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 @@ -109,6 +107,25 @@ const garmin_model_t garmin_models[] = { { NULL, 0, false } }; +// AI-generated (Claude) +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; +} + 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); static dc_status_t garmin_device_close (dc_device_t *abstract); @@ -146,10 +163,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 @@ -428,23 +443,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; diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index d1b78917..42e9c32b 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -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 diff --git a/src/parser.c b/src/parser.c index fd4a263b..2bb7cdf7 100644 --- a/src/parser.c +++ b/src/parser.c @@ -380,6 +380,20 @@ 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; + + if (parser->vtable->type == DC_FAMILY_GARMIN) { + garmin_parser_is_dive(parser, devinfo); + return DC_STATUS_SUCCESS; + } + + 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) { diff --git a/test/fixtures/manifest.txt b/test/fixtures/manifest.txt index ed0e83ee..0ba8efd9 100644 --- a/test/fixtures/manifest.txt +++ b/test/fixtures/manifest.txt @@ -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 From ab5199c81d61d1829f193ad545439d91b6f5c9a4 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 13 Sep 2026 17:49:38 +1200 Subject: [PATCH 2/4] parser: Initialize device info output Signed-off-by: Michael Keller --- src/parser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/parser.c b/src/parser.c index 2bb7cdf7..212f7468 100644 --- a/src/parser.c +++ b/src/parser.c @@ -386,6 +386,8 @@ 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)); + if (parser->vtable->type == DC_FAMILY_GARMIN) { garmin_parser_is_dive(parser, devinfo); return DC_STATUS_SUCCESS; From ad929e791ca980df843117c8724239601095d87b Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Tue, 15 Sep 2026 07:05:56 +1200 Subject: [PATCH 3/4] garmin: consolidate model descriptors Signed-off-by: Michael Keller --- doc/man/Makefile.am | 1 + doc/man/dc_parser_get_device_info.3 | 88 +++++++++++++++++++++++++++++ examples/common.c | 42 +++++++++++++- src/Makefile.am | 2 +- src/descriptor.c | 26 ++------- src/garmin-models.h | 57 +++++++++++++++++++ src/garmin.c | 27 ++------- src/garmin.h | 13 +---- src/garmin_parser.c | 14 +++++ src/parser.c | 12 ++-- 10 files changed, 221 insertions(+), 61 deletions(-) create mode 100644 doc/man/dc_parser_get_device_info.3 create mode 100644 src/garmin-models.h diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am index 95c3bb2c..cade9f70 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -28,6 +28,7 @@ MANPAGES = \ dc_iterator_free.3 \ dc_iterator_next.3 \ dc_parser_destroy.3 \ + dc_parser_get_device_info.3 \ dc_parser_get_datetime.3 \ dc_parser_get_field.3 \ dc_parser_new.3 \ diff --git a/doc/man/dc_parser_get_device_info.3 b/doc/man/dc_parser_get_device_info.3 new file mode 100644 index 00000000..c1984a9f --- /dev/null +++ b/doc/man/dc_parser_get_device_info.3 @@ -0,0 +1,88 @@ +.\" +.\" AI-generated (Claude) +.\" libdivecomputer +.\" +.\" 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 +.\" +.Dd September 15, 2026 +.Dt DC_PARSER_GET_DEVICE_INFO 3 +.Os +.Sh NAME +.Nm dc_parser_get_device_info +.Nd retrieve device information from a parser +.Sh LIBRARY +.Lb libdivecomputer +.Sh SYNOPSIS +.In libdivecomputer/parser.h +.Ft dc_status_t +.Fo dc_parser_get_device_info +.Fa "dc_parser_t *parser" +.Fa "dc_event_devinfo_t *devinfo" +.Fc +.Sh DESCRIPTION +Retrieve parser-provided device information from +.Fa parser , +which must have been created by +.Xr dc_parser_new 3 +or +.Xr dc_parser_new2 3 . +The +.Fa devinfo +output pointer must not be +.Dv NULL . +.Pp +Only Garmin FIT parsers currently support this query. +For a Garmin FIT parser, the +.Va model , +.Va firmware , +and +.Va serial +members are the FIT product model, firmware, and serial values from the +primary +.Dv DEVICE_INFO +record, identified by device index zero. +The generic hardware identifier, +.Va hw_id , +is unavailable for Garmin and remains zero. +.Pp +The complete +.Vt dc_event_devinfo_t +output structure is zero-initialised before the query is performed. +Consequently, fields unavailable in a Garmin FIT file remain zero. +.Sh RETURN VALUES +Returns +.Dv DC_STATUS_SUCCESS +for a Garmin FIT parser, +.Dv DC_STATUS_INVALIDARGS +if +.Fa parser +or +.Fa devinfo +is +.Dv NULL , +or +.Dv DC_STATUS_UNSUPPORTED +for other parser families. +.Sh SEE ALSO +.Xr dc_parser_new 3 , +.Xr dc_parser_new2 3 , +.Xr dc_parser_get_field 3 +.Sh AUTHORS +The +.Lb libdivecomputer +library was written by +.An Jef Driesen , +.Mt jef@libdivecomputer.org . diff --git a/examples/common.c b/examples/common.c index 0950ecb6..9f1383cc 100644 --- a/examples/common.c +++ b/examples/common.c @@ -22,6 +22,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -103,7 +104,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[] = { @@ -217,6 +218,37 @@ dctool_transport_default (dc_descriptor_t *descriptor) return DC_TRANSPORT_NONE; } +// AI-generated (Claude) +static int +dctool_garmin_product_match (const char *name, const char *product) +{ + while (*name && *product) { + if ((unsigned char) product[0] == 0xE2 && + (unsigned char) product[1] == 0x84 && + ((unsigned char) product[2] == 0xA2 || (unsigned char) product[2] == 0xAE)) { + if ((unsigned char) name[0] == 0xE2 && + (unsigned char) name[1] == 0x84 && + (unsigned char) name[2] == (unsigned char) product[2]) + name += 3; + product += 3; + continue; + } + + if (tolower ((unsigned char) *name) != tolower ((unsigned char) *product)) + return 0; + + name++; + product++; + } + + while ((unsigned char) product[0] == 0xE2 && + (unsigned char) product[1] == 0x84 && + ((unsigned char) product[2] == 0xA2 || (unsigned char) product[2] == 0xAE)) + product += 3; + + return *name == '\0' && *product == '\0'; +} + void dctool_event_cb (dc_device_t *device, dc_event_type_t event, const void *data, void *userdata) { @@ -275,11 +307,15 @@ dctool_descriptor_search (dc_descriptor_t **out, const char *name, dc_family_t f size_t n = strlen (vendor); if (strncasecmp (name, vendor, n) == 0 && name[n] == ' ' && - strcasecmp (name + n + 1, product) == 0) + (strcasecmp (name + n + 1, product) == 0 || + (strcasecmp (vendor, "Garmin") == 0 && + dctool_garmin_product_match (name + n + 1, product)))) { current = descriptor; break; - } else if (strcasecmp (name, product) == 0) { + } else if (strcasecmp (name, product) == 0 || + (strcasecmp (vendor, "Garmin") == 0 && + dctool_garmin_product_match (name, product))) { current = descriptor; break; } diff --git a/src/Makefile.am b/src/Makefile.am index 27201495..90c3278b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/descriptor.c b/src/descriptor.c index 919b195c..16c4c372 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -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( \ @@ -546,26 +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). */ - {"Garmin", "Descent™ G1 / G1 Solar", DC_FAMILY_GARMIN, 4005, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "Descent™ G2", DC_FAMILY_GARMIN, 4588, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "Descent™ Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "Descent™ Mk1 APAC", DC_FAMILY_GARMIN, 2991, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "Descent™ Mk2(i)", DC_FAMILY_GARMIN, 3258, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "Descent™ Mk2(i) APAC", DC_FAMILY_GARMIN, 3702, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "Descent™ Mk2 S", DC_FAMILY_GARMIN, 3542, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "Descent™ Mk2 S APAC", DC_FAMILY_GARMIN, 3930, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "Descent™ Mk3(i) 43mm", DC_FAMILY_GARMIN, 4222, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "Descent™ Mk3(i) 51mm", DC_FAMILY_GARMIN, 4223, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "Descent™ X50i", DC_FAMILY_GARMIN, 4518, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "fēnix® 8 43mm", DC_FAMILY_GARMIN, 4534, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "fēnix® 8 47mm / 51mm / tactix® 8 47mm / 51mm / quatix® 8 47mm / 51mm APAC", DC_FAMILY_GARMIN, 4536, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "fēnix® 8 47mm / 51mm / tactix® 8 47mm / 51mm / quatix® 8 47mm / 51mm", DC_FAMILY_GARMIN, 4775, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "fēnix® 8 Pro 47mm / 51mm / MicroLED / quatix® 8 Pro 47mm / 51mm", DC_FAMILY_GARMIN, 4631, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "fēnix® 8 Solar 47mm", DC_FAMILY_GARMIN, 4532, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "fēnix® 8 Solar 51mm / tactix® 8 Solar 51mm APAC", DC_FAMILY_GARMIN, 4533, DC_TRANSPORT_USBSTORAGE, NULL}, - {"Garmin", "fēnix® 8 Solar 51mm / tactix® 8 Solar 51mm", DC_FAMILY_GARMIN, 4776, 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 }, }; diff --git a/src/garmin-models.h b/src/garmin-models.h new file mode 100644 index 00000000..0f9ac19e --- /dev/null +++ b/src/garmin-models.h @@ -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 + +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 */ diff --git a/src/garmin.c b/src/garmin.c index fbecf4ef..d83aec82 100644 --- a/src/garmin.c +++ b/src/garmin.c @@ -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" @@ -82,32 +83,15 @@ 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; @@ -125,6 +109,7 @@ static bool garmin_model_is_mtp_capable(unsigned int 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); diff --git a/src/garmin.h b/src/garmin.h index ca2ca8b4..3924aba4 100644 --- a/src/garmin.h +++ b/src/garmin.h @@ -22,8 +22,6 @@ #ifndef GARMIN_H #define GARMIN_H -#include - #include #include #include @@ -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 diff --git a/src/garmin_parser.c b/src/garmin_parser.c index 8f5f83f2..bc71c2d5 100644 --- a/src/garmin_parser.c +++ b/src/garmin_parser.c @@ -25,6 +25,7 @@ #include #include "garmin.h" +#include "garmin-models.h" #include "context-private.h" #include "parser-private.h" #include "array.h" @@ -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); diff --git a/src/parser.c b/src/parser.c index 212f7468..f975c2d1 100644 --- a/src/parser.c +++ b/src/parser.c @@ -386,14 +386,14 @@ 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)); + memset (devinfo, 0, sizeof (*devinfo)); - if (parser->vtable->type == DC_FAMILY_GARMIN) { - garmin_parser_is_dive(parser, devinfo); - return DC_STATUS_SUCCESS; + switch (parser->vtable->type) { + case DC_FAMILY_GARMIN: + return garmin_parser_get_device_info (parser, devinfo); + default: + return DC_STATUS_UNSUPPORTED; } - - return DC_STATUS_UNSUPPORTED; } dc_status_t From 077fedecd3d92d41b503079c6e70b1ee062a295b Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Wed, 16 Sep 2026 08:00:08 +1200 Subject: [PATCH 4/4] garmin: remove fork-only compatibility extras Remove the dctool product-name matcher and its ctype dependency to avoid carrying fork-only compatibility logic. Remove the parser device-info manpage and its Automake entry; the parser API remains available. Signed-off-by: Michael Keller --- doc/man/Makefile.am | 1 - doc/man/dc_parser_get_device_info.3 | 88 ----------------------------- examples/common.c | 40 +------------ 3 files changed, 2 insertions(+), 127 deletions(-) delete mode 100644 doc/man/dc_parser_get_device_info.3 diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am index cade9f70..95c3bb2c 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -28,7 +28,6 @@ MANPAGES = \ dc_iterator_free.3 \ dc_iterator_next.3 \ dc_parser_destroy.3 \ - dc_parser_get_device_info.3 \ dc_parser_get_datetime.3 \ dc_parser_get_field.3 \ dc_parser_new.3 \ diff --git a/doc/man/dc_parser_get_device_info.3 b/doc/man/dc_parser_get_device_info.3 deleted file mode 100644 index c1984a9f..00000000 --- a/doc/man/dc_parser_get_device_info.3 +++ /dev/null @@ -1,88 +0,0 @@ -.\" -.\" AI-generated (Claude) -.\" libdivecomputer -.\" -.\" 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 -.\" -.Dd September 15, 2026 -.Dt DC_PARSER_GET_DEVICE_INFO 3 -.Os -.Sh NAME -.Nm dc_parser_get_device_info -.Nd retrieve device information from a parser -.Sh LIBRARY -.Lb libdivecomputer -.Sh SYNOPSIS -.In libdivecomputer/parser.h -.Ft dc_status_t -.Fo dc_parser_get_device_info -.Fa "dc_parser_t *parser" -.Fa "dc_event_devinfo_t *devinfo" -.Fc -.Sh DESCRIPTION -Retrieve parser-provided device information from -.Fa parser , -which must have been created by -.Xr dc_parser_new 3 -or -.Xr dc_parser_new2 3 . -The -.Fa devinfo -output pointer must not be -.Dv NULL . -.Pp -Only Garmin FIT parsers currently support this query. -For a Garmin FIT parser, the -.Va model , -.Va firmware , -and -.Va serial -members are the FIT product model, firmware, and serial values from the -primary -.Dv DEVICE_INFO -record, identified by device index zero. -The generic hardware identifier, -.Va hw_id , -is unavailable for Garmin and remains zero. -.Pp -The complete -.Vt dc_event_devinfo_t -output structure is zero-initialised before the query is performed. -Consequently, fields unavailable in a Garmin FIT file remain zero. -.Sh RETURN VALUES -Returns -.Dv DC_STATUS_SUCCESS -for a Garmin FIT parser, -.Dv DC_STATUS_INVALIDARGS -if -.Fa parser -or -.Fa devinfo -is -.Dv NULL , -or -.Dv DC_STATUS_UNSUPPORTED -for other parser families. -.Sh SEE ALSO -.Xr dc_parser_new 3 , -.Xr dc_parser_new2 3 , -.Xr dc_parser_get_field 3 -.Sh AUTHORS -The -.Lb libdivecomputer -library was written by -.An Jef Driesen , -.Mt jef@libdivecomputer.org . diff --git a/examples/common.c b/examples/common.c index 9f1383cc..926c7461 100644 --- a/examples/common.c +++ b/examples/common.c @@ -22,7 +22,6 @@ #include #include #include -#include #ifdef _WIN32 #include @@ -218,37 +217,6 @@ dctool_transport_default (dc_descriptor_t *descriptor) return DC_TRANSPORT_NONE; } -// AI-generated (Claude) -static int -dctool_garmin_product_match (const char *name, const char *product) -{ - while (*name && *product) { - if ((unsigned char) product[0] == 0xE2 && - (unsigned char) product[1] == 0x84 && - ((unsigned char) product[2] == 0xA2 || (unsigned char) product[2] == 0xAE)) { - if ((unsigned char) name[0] == 0xE2 && - (unsigned char) name[1] == 0x84 && - (unsigned char) name[2] == (unsigned char) product[2]) - name += 3; - product += 3; - continue; - } - - if (tolower ((unsigned char) *name) != tolower ((unsigned char) *product)) - return 0; - - name++; - product++; - } - - while ((unsigned char) product[0] == 0xE2 && - (unsigned char) product[1] == 0x84 && - ((unsigned char) product[2] == 0xA2 || (unsigned char) product[2] == 0xAE)) - product += 3; - - return *name == '\0' && *product == '\0'; -} - void dctool_event_cb (dc_device_t *device, dc_event_type_t event, const void *data, void *userdata) { @@ -307,15 +275,11 @@ dctool_descriptor_search (dc_descriptor_t **out, const char *name, dc_family_t f size_t n = strlen (vendor); if (strncasecmp (name, vendor, n) == 0 && name[n] == ' ' && - (strcasecmp (name + n + 1, product) == 0 || - (strcasecmp (vendor, "Garmin") == 0 && - dctool_garmin_product_match (name + n + 1, product)))) + strcasecmp (name + n + 1, product) == 0) { current = descriptor; break; - } else if (strcasecmp (name, product) == 0 || - (strcasecmp (vendor, "Garmin") == 0 && - dctool_garmin_product_match (name, product))) { + } else if (strcasecmp (name, product) == 0) { current = descriptor; break; }