Merge branch 'Garmin-Mk2i' into Subsurface-DS9
This merges the FIT file parsing extensions to actually parse the pressure data from the new Garmin Descent Mk2i. There is more data there about cylinder sizing etc that we can parse, but this is enough to make things useful. At least for the test cases I have access to so far. * Garmin-Mk2i: Garmin Descent Mk2i: add support for parsing cylinder pressure data Garmin: add skeleton for the new fields for the Descent Mk2/Mk2i
This commit is contained in:
commit
1712b99f79
@ -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
|
||||
*/
|
||||
@ -164,6 +195,7 @@ static void garmin_event(struct garmin_parser_t *garmin,
|
||||
[21] = { 3, "Battry Critical" },
|
||||
[22] = { 1, "Safety stop begin" },
|
||||
[23] = { 1, "Approaching deco stop" },
|
||||
[32] = { 1, "Tank battery low" }, // No way to know which tank
|
||||
};
|
||||
dc_sample_value_t sample = {0};
|
||||
|
||||
@ -221,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;
|
||||
}
|
||||
|
||||
@ -236,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -473,6 +530,11 @@ DECLARE_FIELD(RECORD, cns_load, UINT8)
|
||||
}
|
||||
}
|
||||
DECLARE_FIELD(RECORD, n2_load, UINT16) { } // percent
|
||||
DECLARE_FIELD(RECORD, air_time_remaining, UINT32) { } // seconds
|
||||
DECLARE_FIELD(RECORD, pressure_sac, UINT16) { } // 100 * bar/min/pressure
|
||||
DECLARE_FIELD(RECORD, volume_sac, UINT16) { } // 100 * l/min/pressure
|
||||
DECLARE_FIELD(RECORD, rmv, UINT16) { } // 100 * l/min
|
||||
DECLARE_FIELD(RECORD, ascent_rate, SINT32) { } // mm/s (negative is down)
|
||||
|
||||
// DEVICE_SETTINGS
|
||||
DECLARE_FIELD(DEVICE_SETTINGS, utc_offset, UINT32) { garmin->dive.utc_offset = (SINT32) data; } // wrong type in FIT
|
||||
@ -532,6 +594,9 @@ DECLARE_FIELD(DIVE_SUMMARY, end_n2, UINT16) { } // percent
|
||||
DECLARE_FIELD(DIVE_SUMMARY, o2_toxicity, UINT16) { } // OTUs
|
||||
DECLARE_FIELD(DIVE_SUMMARY, dive_number, UINT32) { }
|
||||
DECLARE_FIELD(DIVE_SUMMARY, bottom_time, UINT32) { DC_ASSIGN_FIELD(garmin->cache, DIVETIME, data / 1000); }
|
||||
DECLARE_FIELD(DIVE_SUMMARY, avg_pressure_sac, UINT16) { } // 100 * bar/min/pressure
|
||||
DECLARE_FIELD(DIVE_SUMMARY, avg_volume_sac, UINT16) { } // 100 * L/min/pressure
|
||||
DECLARE_FIELD(DIVE_SUMMARY, avg_rmv, UINT16) { } // 100 * L/min
|
||||
|
||||
// DIVE_SETTINGS
|
||||
DECLARE_FIELD(DIVE_SETTINGS, name, STRING) { }
|
||||
@ -578,6 +643,62 @@ 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) { }
|
||||
|
||||
// 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)
|
||||
{
|
||||
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
|
||||
DECLARE_FIELD(TANK_SUMMARY, end_pressure, UINT16) { } // Bar * 100
|
||||
DECLARE_FIELD(TANK_SUMMARY, volume_used, UINT32) { } // L * 100
|
||||
|
||||
// EVENT
|
||||
DECLARE_FIELD(EVENT, event, ENUM)
|
||||
{
|
||||
@ -601,6 +722,9 @@ DECLARE_FIELD(EVENT, unknown, UINT32)
|
||||
{
|
||||
garmin->record_data.event_unknown = data;
|
||||
}
|
||||
DECLARE_FIELD(EVENT, tank_pressure_reserve, UINT32Z) { } // sensor ID
|
||||
DECLARE_FIELD(EVENT, tank_pressure_critical, UINT32Z) { } // sensor ID
|
||||
DECLARE_FIELD(EVENT, tank_pressure_lost, UINT32Z) { } // sensor ID
|
||||
|
||||
struct msg_desc {
|
||||
unsigned char maxfield;
|
||||
@ -674,7 +798,7 @@ DECLARE_MESG(LAP) = {
|
||||
};
|
||||
|
||||
DECLARE_MESG(RECORD) = {
|
||||
.maxfield = 99,
|
||||
.maxfield = 128,
|
||||
.field = {
|
||||
SET_FIELD(RECORD, 0, position_lat, SINT32), // 180 deg / 2**31
|
||||
SET_FIELD(RECORD, 1, position_long, SINT32), // 180 deg / 2**31
|
||||
@ -690,6 +814,11 @@ DECLARE_MESG(RECORD) = {
|
||||
SET_FIELD(RECORD, 96, ndl, UINT32), // s
|
||||
SET_FIELD(RECORD, 97, cns_load, UINT8), // percent
|
||||
SET_FIELD(RECORD, 98, n2_load, UINT16), // percent
|
||||
SET_FIELD(RECORD, 123, air_time_remaining, UINT32), // seconds
|
||||
SET_FIELD(RECORD, 124, pressure_sac, UINT16), // 100 * bar/min/pressure
|
||||
SET_FIELD(RECORD, 125, volume_sac, UINT16), // 100 * l/min/pressure
|
||||
SET_FIELD(RECORD, 126, rmv, UINT16), // 100 * l/min
|
||||
SET_FIELD(RECORD, 127, ascent_rate, SINT32), // mm/s (negative is down)
|
||||
}
|
||||
};
|
||||
|
||||
@ -704,7 +833,7 @@ DECLARE_MESG(DIVE_GAS) = {
|
||||
};
|
||||
|
||||
DECLARE_MESG(DIVE_SUMMARY) = {
|
||||
.maxfield = 12,
|
||||
.maxfield = 15,
|
||||
.field = {
|
||||
SET_FIELD(DIVE_SUMMARY, 2, avg_depth, UINT32), // mm
|
||||
SET_FIELD(DIVE_SUMMARY, 3, max_depth, UINT32), // mm
|
||||
@ -716,17 +845,23 @@ DECLARE_MESG(DIVE_SUMMARY) = {
|
||||
SET_FIELD(DIVE_SUMMARY, 9, o2_toxicity, UINT16), // OTUs
|
||||
SET_FIELD(DIVE_SUMMARY, 10, dive_number, UINT32),
|
||||
SET_FIELD(DIVE_SUMMARY, 11, bottom_time, UINT32), // ms
|
||||
SET_FIELD(DIVE_SUMMARY, 12, avg_pressure_sac, UINT16), // 100 * bar/min/pressure
|
||||
SET_FIELD(DIVE_SUMMARY, 13, avg_volume_sac, UINT16), // 100 * L/min/pressure
|
||||
SET_FIELD(DIVE_SUMMARY, 14, avg_rmv, UINT16), // 100 * L/min
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_MESG(EVENT) = {
|
||||
.maxfield = 16,
|
||||
.maxfield = 74,
|
||||
.field = {
|
||||
SET_FIELD(EVENT, 0, event, ENUM),
|
||||
SET_FIELD(EVENT, 1, type, ENUM),
|
||||
SET_FIELD(EVENT, 3, data, UINT32),
|
||||
SET_FIELD(EVENT, 4, event_group, UINT8),
|
||||
SET_FIELD(EVENT, 15, unknown, UINT32),
|
||||
SET_FIELD(EVENT, 71, tank_pressure_reserve, UINT32Z), // sensor ID
|
||||
SET_FIELD(EVENT, 72, tank_pressure_critical, UINT32Z), // sensor ID
|
||||
SET_FIELD(EVENT, 73, tank_pressure_lost, UINT32Z), // sensor ID
|
||||
}
|
||||
};
|
||||
|
||||
@ -771,6 +906,39 @@ DECLARE_MESG(DIVE_SETTINGS) = {
|
||||
};
|
||||
DECLARE_MESG(DIVE_ALARM) = { };
|
||||
|
||||
DECLARE_MESG(SENSOR_PROFILE) = {
|
||||
.maxfield = 79,
|
||||
.field = {
|
||||
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),
|
||||
SET_FIELD(SENSOR_PROFILE, 77, volume, UINT16), // CuFt * 10 (PSI) or L * 10 (Bar)
|
||||
SET_FIELD(SENSOR_PROFILE, 78, used_for_gas_rate, ENUM), // was used for SAC calculations?
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_MESG(TANK_UPDATE) = {
|
||||
.maxfield = 2,
|
||||
.field = {
|
||||
SET_FIELD(TANK_UPDATE, 0, sensor, UINT32Z), // sensor ID
|
||||
SET_FIELD(TANK_UPDATE, 1, pressure, UINT16), // pressure in Bar * 100
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_MESG(TANK_SUMMARY) = {
|
||||
.maxfield = 4,
|
||||
.field = {
|
||||
SET_FIELD(TANK_SUMMARY, 0, sensor, UINT32Z), // sensor ID
|
||||
SET_FIELD(TANK_SUMMARY, 1, start_pressure, UINT16), // Bar * 100
|
||||
SET_FIELD(TANK_SUMMARY, 2, end_pressure, UINT16), // Bar * 100
|
||||
SET_FIELD(TANK_SUMMARY, 3, volume_used, UINT32), // L * 100
|
||||
}
|
||||
};
|
||||
|
||||
// Unknown global message ID's..
|
||||
DECLARE_MESG(WTF_13) = { };
|
||||
DECLARE_MESG(WTF_22) = { };
|
||||
@ -809,12 +977,16 @@ static const struct {
|
||||
SET_MESG(140, WTF_140),
|
||||
SET_MESG(141, WTF_141),
|
||||
|
||||
SET_MESG(147, SENSOR_PROFILE),
|
||||
|
||||
SET_MESG(216, WTF_216),
|
||||
SET_MESG(233, WTF_233),
|
||||
SET_MESG(258, DIVE_SETTINGS),
|
||||
SET_MESG(259, DIVE_GAS),
|
||||
SET_MESG(262, DIVE_ALARM),
|
||||
SET_MESG(268, DIVE_SUMMARY),
|
||||
SET_MESG(319, TANK_UPDATE),
|
||||
SET_MESG(323, TANK_SUMMARY),
|
||||
};
|
||||
|
||||
#define MSG_NAME_LEN 16
|
||||
@ -1141,6 +1313,7 @@ traverse_data(struct garmin_parser_t *garmin)
|
||||
if (garmin->record_data.pending)
|
||||
flush_pending_record(garmin);
|
||||
}
|
||||
|
||||
return DC_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
@ -1211,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)
|
||||
{
|
||||
@ -1224,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);
|
||||
@ -1238,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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user