Skip to content
Merged
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
20 changes: 20 additions & 0 deletions include/libdivecomputer/descriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,26 @@ dc_descriptor_get_transports (const dc_descriptor_t *descriptor);
int
dc_descriptor_filter (const dc_descriptor_t *descriptor, dc_transport_t transport, const void *userdata);

/**
* Find a descriptor by hardware identifier.
*
* Some device families share a coarse model number across multiple marketed
* products. When a device reports a hardware identifier (via
* DC_EVENT_DEVINFO.hw_id), this function can find a more specific
* descriptor than the model number alone provides. This is best-effort:
* returns NULL if the hardware id is unknown or not in the table.
*
* The returned descriptor is a direct reference to an internal table entry
* and must be released with dc_descriptor_free(), which is safe to call on
* such a reference.
*
* @param[in] family The device family type.
* @param[in] hw_id The hardware identifier from DC_EVENT_DEVINFO.
* @returns A descriptor on success, or NULL if no match.
*/
dc_descriptor_t *
dc_descriptor_find_by_hw_id (dc_family_t family, unsigned int hw_id);
Comment on lines +144 to +145

#ifdef __cplusplus
}
#endif /* __cplusplus */
Expand Down
7 changes: 7 additions & 0 deletions include/libdivecomputer/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ typedef struct dc_event_devinfo_t {
unsigned int model;
unsigned int firmware;
unsigned int serial;
/* Generic hardware identifier, 0 = unknown/not available.
* On live download from devices that support it, this is populated
* with the device's hardware type identifier (e.g. Shearwater FWID
* from RDBI 0x8050). It supplements the coarse model field for
* consumer-side sub-model differentiation. Consumers that do not use
* this field are unaffected; the model field remains authoritative. */
unsigned int hw_id;
Comment on lines +55 to +61
} dc_event_devinfo_t;

typedef struct dc_event_clock_t {
Expand Down
1 change: 0 additions & 1 deletion src/cochran_commander.c
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,6 @@ cochran_commander_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
devinfo.serial = array_uint32_word_be(data.config + layout->cf_serial_number);
else
devinfo.serial = array_uint32_le(data.config + layout->cf_serial_number);

device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);

unsigned int head_dive = 0, tail_dive = 0, dive_count = 0;
Expand Down
62 changes: 62 additions & 0 deletions src/descriptor.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,38 @@ static const dc_iterator_vtable_t dc_descriptor_iterator_vtable = {
* actually used to identify individual models, identical values are assigned.
*/

/* Hardware-identifier to descriptor mapping.
*
* Some device families share a coarse model number (DC_EVENT_DEVINFO.model)
* across multiple marketed products; the hardware identifier (hw_id)
* from DC_EVENT_DEVINFO can refine the selection to a specific descriptor.
* This table is best-effort: the FWID is only available on live download and
* may change across firmware updates. Consumers should treat a NULL result
* from dc_descriptor_find_by_hw_id() as "use the coarse model descriptor". */
typedef struct {
dc_family_t family;
unsigned int model;
unsigned int hw_id;
const char *product;
} dc_hw_id_entry_t;

static const dc_hw_id_entry_t g_hw_id_map[] = {
/* Shearwater Petrel family.
* Petrel 1 (model=3): hardware ids 0x0404, 0x0909 */
{DC_FAMILY_SHEARWATER_PETREL, 3, 0x0404, "Petrel"},
{DC_FAMILY_SHEARWATER_PETREL, 3, 0x0909, "Petrel"},
/* Petrel 2 (model=3): hardware ids 0x0505, 0x0808, 0x0838, 0x08A5,
* 0x0B0B, 0x7828, 0x7B2C, 0x8838 */
{DC_FAMILY_SHEARWATER_PETREL, 3, 0x0505, "Petrel 2"},
{DC_FAMILY_SHEARWATER_PETREL, 3, 0x0808, "Petrel 2"},
{DC_FAMILY_SHEARWATER_PETREL, 3, 0x0838, "Petrel 2"},
{DC_FAMILY_SHEARWATER_PETREL, 3, 0x08A5, "Petrel 2"},
{DC_FAMILY_SHEARWATER_PETREL, 3, 0x0B0B, "Petrel 2"},
{DC_FAMILY_SHEARWATER_PETREL, 3, 0x7828, "Petrel 2"},
{DC_FAMILY_SHEARWATER_PETREL, 3, 0x7B2C, "Petrel 2"},
{DC_FAMILY_SHEARWATER_PETREL, 3, 0x8838, "Petrel 2"},
};

static const dc_descriptor_t g_descriptors[] = {
/* Suunto Solution */
{"Suunto", "Solution", DC_FAMILY_SUUNTO_SOLUTION, 0, DC_TRANSPORT_SERIAL, NULL},
Expand Down Expand Up @@ -1055,6 +1087,36 @@ dc_descriptor_free (dc_descriptor_t *descriptor)
return;
}

dc_descriptor_t *
dc_descriptor_find_by_hw_id (dc_family_t family, unsigned int hw_id)
{
if (hw_id == 0)
return NULL;

for (size_t i = 0; i < C_ARRAY_SIZE(g_hw_id_map); i++) {
if (g_hw_id_map[i].family != family || g_hw_id_map[i].hw_id != hw_id)
continue;

/* Matched — find the corresponding entry in g_descriptors[] by
* family, model, and product name. Returning a direct pointer
* into the static table is safe: dc_descriptor_free() is a
* no-op and g_descriptors[] has static storage duration. */
for (size_t j = 0; j < C_ARRAY_SIZE(g_descriptors); j++) {
if (g_descriptors[j].type == family &&
g_descriptors[j].model == g_hw_id_map[i].model &&
strcmp(g_descriptors[j].product, g_hw_id_map[i].product) == 0) {
return (dc_descriptor_t *) &g_descriptors[j];
}
}
/* hw_id matched the map but the descriptor was not found —
* product name mismatch or g_descriptors[] was edited without
* updating g_hw_id_map[]. */
return NULL;
}

return NULL;
}

const char *
dc_descriptor_get_vendor (const dc_descriptor_t *descriptor)
{
Expand Down
3 changes: 3 additions & 0 deletions src/device-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ dc_device_deallocate (dc_device_t *device);
void
device_event_emit (dc_device_t *device, dc_event_type_t event, const void *data);

void
device_set_hw_id (dc_device_t *device, unsigned int hw_id);

int
device_is_cancelled (dc_device_t *device);

Expand Down
15 changes: 14 additions & 1 deletion src/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,13 @@ dc_device_close (dc_device_t *device)
}


// AI-generated (Claude)
void
device_set_hw_id (dc_device_t *device, unsigned int hw_id)
{
device->devinfo.hw_id = hw_id;
}

void
device_event_emit (dc_device_t *device, dc_event_type_t event, const void *data)
{
Expand Down Expand Up @@ -493,7 +500,13 @@ device_event_emit (dc_device_t *device, dc_event_type_t event, const void *data)
// Cache the event data.
switch (event) {
case DC_EVENT_DEVINFO:
device->devinfo = *(const dc_event_devinfo_t *) data;
{
const dc_event_devinfo_t *devinfo = (const dc_event_devinfo_t *) data;
device->devinfo.model = devinfo->model;
device->devinfo.firmware = devinfo->firmware;
device->devinfo.serial = devinfo->serial;
data = &device->devinfo;
}
break;
case DC_EVENT_CLOCK:
device->clock = *(const dc_event_clock_t *) data;
Expand Down
1 change: 1 addition & 0 deletions src/libdivecomputer.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dc_descriptor_get_type
dc_descriptor_get_model
dc_descriptor_get_transports
dc_descriptor_filter
dc_descriptor_find_by_hw_id

dc_iostream_get_transport
dc_iostream_set_timeout
Expand Down
107 changes: 0 additions & 107 deletions src/shearwater_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -762,110 +762,3 @@ shearwater_common_timesync_utc (shearwater_common_device_t *device, const dc_dat

return status;
}

dc_status_t shearwater_common_get_model(shearwater_common_device_t *device, unsigned int *model)
{
// Read the hardware type.
unsigned char rsp_hardware[2] = {0};
dc_status_t status = shearwater_common_rdbi (device, ID_HARDWARE, rsp_hardware, sizeof(rsp_hardware), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (device->base.context, "Failed to read the hardware type.");
return status;
}

// Convert and map to the model number.
unsigned int hardware = array_uint16_be (rsp_hardware);

DEBUG(device->base.context, "Hardware type: 0x%04x", hardware);

switch (hardware) {
case 0x0101:
case 0x0202:
*model = PREDATOR;
break;
case 0x0404:
case 0x0909:
*model = PETREL;
break;
case 0x0505:
case 0x0808:
case 0x0838:
case 0x08A5:
case 0x0B0B:
case 0x7828:
case 0x7B2C:
case 0x8838:
*model = PETREL2;
break;
case 0xB407:
case 0xB429:
case 0xB469:
case 0x3C3D:
*model = PETREL3;
break;
case 0x0606:
case 0x0A0A:
*model = NERD;
break;
case 0x0E0D:
case 0x7E2D:
*model = NERD2;
break;
case 0x0707:
*model = PERDIX;
break;
case 0x0C0D:
case 0x425B:
case 0x7C2D:
case 0x8D6C:
*model = PERDIXAI;
break;
case 0x704C:
case 0x924C:
case 0x9C64:
case 0xC407:
case 0xC429:
case 0xC964:
*model = PERDIX2;
break;
case 0x39C2:
case 0x4AB1:
*model = PERDIX3;
break;
case 0x1F0A:
case 0x1F0F:
case 0x0F0F:
case 0x1F10:
case 0x1F1A:
*model = TERIC;
break;
case 0x1512:
case 0x1613:
case 0x2623:
case 0x63A5:
*model = PEREGRINE;
break;
case 0x1712:
case 0x813A:
*model = PEREGRINE_TX;
break;
case 0xC0E0:
*model = TERN;
break;
default:
// Unknown hardware type: fall back to reading the model number directly from the device.
WARNING (device->base.context, "Unknown hardware type 0x%04x, falling back to ID_MODEL.", hardware);
{
unsigned char rsp_model = 0;
dc_status_t rc = shearwater_common_rdbi (device, ID_MODEL, &rsp_model, sizeof(rsp_model), NULL);
if (rc != DC_STATUS_SUCCESS) {
ERROR (device->base.context, "Failed to read the model number.");
return rc;
}
*model = rsp_model;
}
break;
}

return status;
}
5 changes: 3 additions & 2 deletions src/shearwater_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ extern "C" {

#define PREDATOR 2
#define PETREL 3
/* PETREL2: Petrel 2 hardware reports ID_MODEL=PETREL (3); there is no
* distinct Product-Version value for it. FWID-based sub-model distinction
* is exposed via devinfo.hw_id on live download. */
#define PETREL2 PETREL
#define NERD 4
#define PERDIX 5
Expand Down Expand Up @@ -93,8 +96,6 @@ dc_status_t shearwater_common_can_wdbi (shearwater_common_device_t *device, dc_b

dc_status_t shearwater_common_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime);

dc_status_t shearwater_common_get_model(shearwater_common_device_t *device, unsigned int *model);

#ifdef __cplusplus
}
#endif /* __cplusplus */
Expand Down
37 changes: 28 additions & 9 deletions src/shearwater_petrel.c
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,33 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
// Convert to a number.
unsigned int firmware = str2num (rsp_firmware, rsp_firmware_length, 1);

unsigned int model = 0;
rc = shearwater_common_get_model (&device->base, &model);
if (rc != DC_STATUS_SUCCESS)
// Read the model number (ID_MODEL, RDBI 0x8060) — Product-Version byte,
// authoritative for the coarse product family (aligns with upstream libdc).
unsigned char rsp_model = 0;
rc = shearwater_common_rdbi (&device->base, ID_MODEL, &rsp_model, sizeof(rsp_model), NULL);
if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the model number.");
return rc;
}

// Attempt to read the hardware type (ID_HARDWARE, RDBI 0x8050) for
// consumer-side sub-model differentiation (e.g. Petrel 1 vs Petrel 2).
// Best-effort: FWID can change across firmware updates and stored logs
// do not carry it. Failure is non-fatal; hw_id remains 0.
unsigned char rsp_hardware[2] = {0};
unsigned int fwid = 0;
dc_status_t hw_rc = shearwater_common_rdbi (&device->base, ID_HARDWARE, rsp_hardware, sizeof(rsp_hardware), NULL);
if (hw_rc == DC_STATUS_SUCCESS) {
fwid = array_uint16_be (rsp_hardware);
DEBUG (abstract->context, "Hardware type (FWID): 0x%04x", fwid);
}

// Emit a device info event.
dc_event_devinfo_t devinfo;
devinfo.model = model;
devinfo.model = rsp_model;
devinfo.firmware = firmware;
devinfo.serial = array_uint32_be (serial);
device_set_hw_id (abstract, fwid);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);

// Read the logbook type
Expand Down Expand Up @@ -356,15 +373,17 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
static dc_status_t
shearwater_petrel_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime)
{
dc_status_t status = DC_STATUS_SUCCESS;
shearwater_common_device_t *device = (shearwater_common_device_t *) abstract;

unsigned int model = 0;
status = shearwater_common_get_model (device, &model);
if (status != DC_STATUS_SUCCESS)
// Read ID_MODEL (RDBI 0x8060) directly to determine time-sync variant.
unsigned char rsp_model = 0;
dc_status_t status = shearwater_common_rdbi (device, ID_MODEL, &rsp_model, sizeof(rsp_model), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the model number.");
return status;
}

if (model == TERIC) {
if (rsp_model == TERIC) {
return shearwater_common_timesync_utc (device, datetime);
} else {
return shearwater_common_timesync_local (device, datetime);
Expand Down
5 changes: 5 additions & 0 deletions src/shearwater_predator_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,11 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
}

// Get the correct model number from the final block.
// Product-Version byte from the device's final record — authoritative for
// the coarse product family. This value is not the FWID; stored logs do not
// carry the FWID, so sub-model distinctions (e.g. Petrel 1 vs Petrel 2) are
// not recoverable on re-parse. Consumer-side refinement uses the FWID
// exposed via devinfo.hw_id on live download.
if (parser->final != UNDEFINED) {
parser->model = data[parser->final + 13];
DEBUG (abstract->context, "Device: model=%u, serial=%u, firmware=%u",
Expand Down