From 89b1cc4f28c73a30920816201d4c90572aa95fe1 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 30 Oct 2020 12:37:19 -0700 Subject: [PATCH] Garmin Descent Mk2i: add support for parsing cylinder pressure data Very early trial at parsing the pressure data from the Garmin tank pods. It seems to work for the test-cases I have. Signed-off-by: Linus Torvalds --- src/garmin_parser.c | 142 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 10 deletions(-) diff --git a/src/garmin_parser.c b/src/garmin_parser.c index a5c6ad1..dbc7c22 100644 --- a/src/garmin_parser.c +++ b/src/garmin_parser.c @@ -50,6 +50,14 @@ struct pos { int lat, lon; }; +#define MAX_SENSORS 6 +struct garmin_sensor { + unsigned int sensor_id; + const char *sensor_name; + unsigned char sensor_enabled, sensor_units, sensor_used_for_gas_rate; + unsigned int sensor_rated_pressure, sensor_reserve_pressure, sensor_volume; +}; + #define MAXTYPE 16 #define MAXGASES 16 #define MAXSTRINGS 32 @@ -77,6 +85,11 @@ struct record_data { // RECORD_DECO_MODEL unsigned char model, gf_low, gf_high; + + // RECORD_SENSOR_PROFILE has no data, fills in dive.sensor[nr_sensor] + + // RECORD_TANK_UPDATE + unsigned int sensor, pressure; }; #define RECORD_GASMIX 1 @@ -84,6 +97,8 @@ struct record_data { #define RECORD_EVENT 4 #define RECORD_DEVICE_INFO 8 #define RECORD_DECO_MODEL 16 +#define RECORD_SENSOR_PROFILE 32 +#define RECORD_TANK_UPDATE 64 typedef struct garmin_parser_t { dc_parser_t base; @@ -106,6 +121,8 @@ typedef struct garmin_parser_t { unsigned int profile; unsigned int time; int utc_offset, time_offset; + unsigned int nr_sensor; + struct garmin_sensor sensor[MAX_SENSORS]; } dive; // I count nine (!) different GPS fields Hmm. @@ -128,6 +145,20 @@ typedef struct garmin_parser_t { typedef int (*garmin_data_cb_t)(unsigned char type, const unsigned char *data, int len, void *user); +static inline struct garmin_sensor *current_sensor(garmin_parser_t *garmin) +{ + return garmin->dive.sensor + garmin->dive.nr_sensor; +} + +static int find_tank_index(garmin_parser_t *garmin, unsigned int sensor_id) +{ + for (int i = 0; i < garmin->dive.nr_sensor; i++) { + if (garmin->dive.sensor[i].sensor_id == sensor_id) + return i; + } + return 0; +} + /* * Decode the event. Numbers from Wojtek's fit2subs python script */ @@ -222,6 +253,23 @@ static void flush_pending_record(struct garmin_parser_t *garmin) } if (pending & RECORD_DECO_MODEL) dc_field_add_string_fmt(&garmin->cache, "Deco model", "Buhlmann ZHL-16C %u/%u", record->gf_low, record->gf_high); + + // End of sensor record just increments nr_sensor, + // so that the next sensor record will start + // filling in the next one. + // + // NOTE! This only happens for tank pods, other + // sensors will just overwrite each other. + // + // Also note that the last sensor is just for + // scratch use, so that the sensor record can + // always fill in dive.sensor[nr_sensor] with + // no checking. + if (pending & RECORD_SENSOR_PROFILE) { + if (garmin->dive.nr_sensor < MAX_SENSORS-1) + garmin->dive.nr_sensor++; + } + return; } @@ -237,6 +285,14 @@ static void flush_pending_record(struct garmin_parser_t *garmin) garmin_event(garmin, record->event_nr, record->event_type, record->event_group, record->event_data, record->event_unknown); } + + if (pending & RECORD_TANK_UPDATE) { + dc_sample_value_t sample = {0}; + + sample.pressure.tank = find_tank_index(garmin, record->sensor); + sample.pressure.value = record->pressure / 100.0; + garmin->callback(DC_SAMPLE_PRESSURE, sample, garmin->userdata); + } } @@ -587,17 +643,56 @@ DECLARE_FIELD(DIVE_SETTINGS, safety_stop_time, UINT16) { } DECLARE_FIELD(DIVE_SETTINGS, heart_rate_source_type, ENUM) { } DECLARE_FIELD(DIVE_SETTINGS, hear_rate_device_type, UINT8) { } -DECLARE_FIELD(SENSOR_PROFILE, ant_channel_id, UINT32Z) { } -DECLARE_FIELD(SENSOR_PROFILE, name, STRING) { } -DECLARE_FIELD(SENSOR_PROFILE, enabled, ENUM) { } -DECLARE_FIELD(SENSOR_PROFILE, pressure_units, ENUM) { } // 0 is PSI, 1 is KPA (unused), 2 is Bar -DECLARE_FIELD(SENSOR_PROFILE, rated_pressure, UINT16) { } -DECLARE_FIELD(SENSOR_PROFILE, reserve_pressure, UINT16) { } -DECLARE_FIELD(SENSOR_PROFILE, volume, UINT16) { } // CuFt * 10 (PSI) or L * 10 -DECLARE_FIELD(SENSOR_PROFILE, used_for_gas_rate, ENUM) { } // was used for SAC calculations? +// SENSOR_PROFILE record for each ANT/BLE sensor. +// We only care about sensor type 28 - Garmin tank pod. +DECLARE_FIELD(SENSOR_PROFILE, ant_channel_id, UINT32Z) +{ + current_sensor(garmin)->sensor_id = data; +} +DECLARE_FIELD(SENSOR_PROFILE, name, STRING) { } // We don't pass in string types correctly +DECLARE_FIELD(SENSOR_PROFILE, enabled, ENUM) +{ + current_sensor(garmin)->sensor_enabled = data; +} +DECLARE_FIELD(SENSOR_PROFILE, sensor_type, UINT8) +{ + // 28 is tank pod + // start filling in next sensor after this record + if (data == 28) + garmin->record_data.pending |= RECORD_SENSOR_PROFILE; +} +DECLARE_FIELD(SENSOR_PROFILE, pressure_units, ENUM) +{ + // 0 is PSI, 1 is KPA (unused), 2 is Bar + current_sensor(garmin)->sensor_units = data; +} +DECLARE_FIELD(SENSOR_PROFILE, rated_pressure, UINT16) +{ + current_sensor(garmin)->sensor_rated_pressure = data; +} +DECLARE_FIELD(SENSOR_PROFILE, reserve_pressure, UINT16) +{ + current_sensor(garmin)->sensor_reserve_pressure = data; +} +DECLARE_FIELD(SENSOR_PROFILE, volume, UINT16) +{ + current_sensor(garmin)->sensor_volume = data; +} +DECLARE_FIELD(SENSOR_PROFILE, used_for_gas_rate, ENUM) +{ + current_sensor(garmin)->sensor_used_for_gas_rate = data; +} -DECLARE_FIELD(TANK_UPDATE, sensor, UINT32Z) { } // sensor ID -DECLARE_FIELD(TANK_UPDATE, pressure, UINT16) { } // pressure in Bar * 100 +DECLARE_FIELD(TANK_UPDATE, sensor, UINT32Z) +{ + garmin->record_data.sensor = data; +} + +DECLARE_FIELD(TANK_UPDATE, pressure, UINT16) +{ + garmin->record_data.pressure = data; + garmin->record_data.pending |= RECORD_TANK_UPDATE; +} DECLARE_FIELD(TANK_SUMMARY, sensor, UINT32Z) { } // sensor ID DECLARE_FIELD(TANK_SUMMARY, start_pressure, UINT16) { } // Bar * 100 @@ -817,6 +912,7 @@ DECLARE_MESG(SENSOR_PROFILE) = { SET_FIELD(SENSOR_PROFILE, 0, ant_channel_id, UINT32Z), // derived from the number engraved on the side SET_FIELD(SENSOR_PROFILE, 2, name, STRING), SET_FIELD(SENSOR_PROFILE, 3, enabled, ENUM), + SET_FIELD(SENSOR_PROFILE, 52, sensor_type, UINT8), // 28 is tank pod SET_FIELD(SENSOR_PROFILE, 74, pressure_units, ENUM), // 0 is PSI, 1 is KPA (unused), 2 is Bar SET_FIELD(SENSOR_PROFILE, 75, rated_pressure, UINT16), SET_FIELD(SENSOR_PROFILE, 76, reserve_pressure, UINT16), @@ -1217,6 +1313,7 @@ traverse_data(struct garmin_parser_t *garmin) if (garmin->record_data.pending) flush_pending_record(garmin); } + return DC_STATUS_SUCCESS; } @@ -1287,6 +1384,11 @@ garmin_parser_is_dive (dc_parser_t *abstract, const unsigned char *data, unsigne } } +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); +} + static dc_status_t garmin_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) { @@ -1300,6 +1402,7 @@ garmin_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsign memset(&garmin->cache, 0, sizeof(garmin->cache)); traverse_data(garmin); + // These seem to be the "real" GPS dive coordinates add_gps_string(garmin, "GPS1", &garmin->gps.SESSION.entry); add_gps_string(garmin, "GPS2", &garmin->gps.SESSION.exit); @@ -1314,6 +1417,25 @@ garmin_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsign add_gps_string(garmin, "Record GPS", &garmin->gps.RECORD); + // We have no idea about gas mixes vs tanks + for (int i = 0; i < garmin->dive.nr_sensor; i++) { + // DC_ASSIGN_IDX(garmin->cache, tankinfo, i, ..); + // DC_ASSIGN_IDX(garmin->cache, tanksize, i, ..); + // DC_ASSIGN_IDX(garmin->cache, tankworkingpressure, i, ..); + } + + // Hate hate hate gasmix vs tank counts. + // + // There's no way to match them up unless they are an identity + // mapping, so having two different ones doesn't actually work. + if (garmin->dive.nr_sensor > garmin->cache.GASMIX_COUNT) + DC_ASSIGN_FIELD(garmin->cache, GASMIX_COUNT, garmin->dive.nr_sensor); + + for (int i = 0; i < garmin->dive.nr_sensor; i++) { + static const char *name[] = { "Sensor 1", "Sensor 2", "Sensor 3", "Sensor 4", "Sensor 5" }; + add_sensor_string(garmin, name[i], garmin->dive.sensor+i); + } + return DC_STATUS_SUCCESS; }