From 7fb943ae7fd8448ceb0c8aa3761061dbc3c44580 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 30 Jun 2022 22:03:28 +0200 Subject: [PATCH] Add support for parsing the decompression model Report the decompression algorithm (Buhlmann, VPM, RGBM or DCIEM), and if available also the parameters. For now only the conservatism setting is supported, and for the Buhlmann algorithm also the Gradient Factors (GF). --- examples/output_xml.c | 24 ++++++++++ include/libdivecomputer/parser.h | 40 +++++++++++++++- src/divesystem_idive_parser.c | 56 +++++++++++++++++++++++ src/hw_ostc_parser.c | 78 ++++++++++++++++++++++++++++++++ src/liquivision_lynx_parser.c | 20 ++++++++ src/seac_screen_parser.c | 26 +++++++++++ src/shearwater_predator_parser.c | 30 ++++++++++++ src/suunto_d9_parser.c | 15 ++++++ src/suunto_eonsteel_parser.c | 18 ++++++++ src/suunto_vyper_parser.c | 5 ++ 10 files changed, 311 insertions(+), 1 deletion(-) diff --git a/examples/output_xml.c b/examples/output_xml.c index 854646b..7d76cb1 100644 --- a/examples/output_xml.c +++ b/examples/output_xml.c @@ -386,6 +386,30 @@ dctool_xml_output_write (dctool_output_t *abstract, dc_parser_t *parser, const u names[divemode]); } + // Parse the deco model. + message ("Parsing the deco model.\n"); + dc_decomodel_t decomodel = {DC_DECOMODEL_NONE}; + status = dc_parser_get_field (parser, DC_FIELD_DECOMODEL, 0, &decomodel); + if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) { + ERROR ("Error parsing the deco model."); + goto cleanup; + } + + if (status != DC_STATUS_UNSUPPORTED) { + const char *names[] = {"none", "buhlmann", "vpm", "rgbm", "dciem"}; + fprintf (output->ostream, "%s\n", + names[decomodel.type]); + if (decomodel.type == DC_DECOMODEL_BUHLMANN && + (decomodel.params.gf.low != 0 || decomodel.params.gf.high != 0)) { + fprintf (output->ostream, "%u/%u\n", + decomodel.params.gf.low, decomodel.params.gf.high); + } + if (decomodel.conservatism) { + fprintf (output->ostream, "%d\n", + decomodel.conservatism); + } + } + // Parse the salinity. message ("Parsing the salinity.\n"); dc_salinity_t salinity = {DC_WATER_FRESH, 0.0}; diff --git a/include/libdivecomputer/parser.h b/include/libdivecomputer/parser.h index 1b53fd3..dc81317 100644 --- a/include/libdivecomputer/parser.h +++ b/include/libdivecomputer/parser.h @@ -62,7 +62,8 @@ typedef enum dc_field_type_t { DC_FIELD_TEMPERATURE_MAXIMUM, DC_FIELD_TANK_COUNT, DC_FIELD_TANK, - DC_FIELD_DIVEMODE + DC_FIELD_DIVEMODE, + DC_FIELD_DECOMODEL, } dc_field_type_t; typedef enum parser_sample_event_t { @@ -186,6 +187,43 @@ typedef struct dc_tank_t { double endpressure; /* End pressure (bar) */ } dc_tank_t; +typedef enum dc_decomodel_type_t { + DC_DECOMODEL_NONE, + DC_DECOMODEL_BUHLMANN, + DC_DECOMODEL_VPM, + DC_DECOMODEL_RGBM, + DC_DECOMODEL_DCIEM, +} dc_decomodel_type_t; + +/* + * Decompression model + * + * The type field contains the decompression algorithm. + * + * The (optional) conservatism field contains the personal adjustment + * setting of the algorithm. The exact interpretation depends on the + * dive computer, but the default value (zero) will typically correspond + * to the neutral setting, while a positive value is more conservative + * and a negative value more aggressive. + * + * The (optional) params field contains the parameters of the algorithm: + * + * DC_DECOMODEL_BUHLMANN: The Gradient Factor (GF) parameters low and + * high. For a pure Buhlmann algorithm (e.g. without GF enabled), both + * values are 100. If GF are enabled, but the actual parameter values + * are not available from the dive computer, both values are zero. + */ +typedef struct dc_decomodel_t { + dc_decomodel_type_t type; + int conservatism; + union { + struct { + unsigned int high; + unsigned int low; + } gf; + } params; +} dc_decomodel_t; + typedef union dc_sample_value_t { unsigned int time; double depth; diff --git a/src/divesystem_idive_parser.c b/src/divesystem_idive_parser.c index 5ace8f4..f0d7700 100644 --- a/src/divesystem_idive_parser.c +++ b/src/divesystem_idive_parser.c @@ -48,6 +48,9 @@ #define FREEDIVE 4 #define INVALID 0xFFFFFFFF +#define BUHLMANN 0 +#define VPM 1 + typedef struct divesystem_idive_parser_t divesystem_idive_parser_t; typedef struct divesystem_idive_gasmix_t { @@ -74,6 +77,9 @@ struct divesystem_idive_parser_t { unsigned int ntanks; divesystem_idive_gasmix_t gasmix[NGASMIXES]; divesystem_idive_tank_t tank[NTANKS]; + unsigned int algorithm; + unsigned int gf_low; + unsigned int gf_high; }; static dc_status_t divesystem_idive_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -129,6 +135,10 @@ divesystem_idive_parser_create (dc_parser_t **out, dc_context_t *context, unsign parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; } + parser->algorithm = INVALID; + parser->gf_low = INVALID; + parser->gf_high = INVALID; + *out = (dc_parser_t*) parser; @@ -157,6 +167,9 @@ divesystem_idive_parser_set_data (dc_parser_t *abstract, const unsigned char *da parser->tank[i].beginpressure = 0; parser->tank[i].endpressure = 0; } + parser->algorithm = INVALID; + parser->gf_low = INVALID; + parser->gf_high = INVALID; return DC_STATUS_SUCCESS; } @@ -280,6 +293,7 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -343,6 +357,25 @@ divesystem_idive_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + if (parser->algorithm == INVALID) + return DC_STATUS_UNSUPPORTED; + switch (parser->algorithm) { + case BUHLMANN: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_low; + decomodel->params.gf.high = parser->gf_high; + break; + case VPM: + decomodel->type = DC_DECOMODEL_VPM; + decomodel->conservatism = 0; + break; + default: + ERROR (abstract->context, "Unknown deco algorithm %02x.", parser->algorithm); + return DC_STATUS_DATAFORMAT; + } + break; default: return DC_STATUS_UNSUPPORTED; } @@ -371,6 +404,10 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba unsigned int divemode = INVALID; unsigned int tank_previous = INVALID; unsigned int tank_idx = INVALID; + unsigned int algorithm = INVALID; + unsigned int algorithm_previous = INVALID; + unsigned int gf_low = INVALID; + unsigned int gf_high = INVALID; unsigned int firmware = 0; unsigned int apos4 = 0; @@ -433,6 +470,22 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba divemode = mode; } + // Deco model + unsigned int s_algorithm = data[offset + 14]; + unsigned int s_gf_high = data[offset + 15]; + unsigned int s_gf_low = data[offset + 16]; + if (s_algorithm != algorithm_previous) { + if (algorithm_previous != INVALID) { + WARNING (abstract->context, "Deco algorithm changed from %02x to %02x.", algorithm_previous, s_algorithm); + } + algorithm_previous = s_algorithm; + } + if (algorithm == INVALID) { + algorithm = s_algorithm; + gf_low = s_gf_low; + gf_high = s_gf_high; + } + // Setpoint if (mode == SCR || mode == CCR) { unsigned int setpoint = array_uint16_le (data + offset + 19); @@ -560,6 +613,9 @@ divesystem_idive_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callba parser->maxdepth = maxdepth; parser->divetime = time; parser->divemode = divemode; + parser->algorithm = algorithm; + parser->gf_low = gf_low; + parser->gf_high = gf_high; parser->cached = 1; return DC_STATUS_SUCCESS; diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index f1092c0..06470d7 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -69,6 +69,10 @@ #define OSTC3_APNEA 3 #define OSTC3_PSCR 4 +#define OSTC3_ZHL16 0 +#define OSTC3_ZHL16_GF 1 +#define OSTC4_VPM 2 + #define OSTC4 0x3B #define OSTC3FW(major,minor) ( \ @@ -97,6 +101,8 @@ typedef struct hw_ostc_layout_t { unsigned int salinity; unsigned int avgdepth; unsigned int duration; + unsigned int gf; + unsigned int decomodel; unsigned int divemode; } hw_ostc_layout_t; @@ -147,6 +153,8 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc = { 43, /* salinity */ 45, /* avgdepth */ 47, /* duration */ + 49, /* gf */ + UNDEFINED, /* decomodel */ 51, /* divemode */ }; @@ -160,6 +168,8 @@ static const hw_ostc_layout_t hw_ostc_layout_frog = { 43, /* salinity */ 45, /* avgdepth */ 47, /* duration */ + 49, /* gf */ + UNDEFINED, /* decomodel */ 51, /* divemode */ }; @@ -173,6 +183,8 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc3 = { 70, /* salinity */ 73, /* avgdepth */ 75, /* duration */ + 77, /* gf */ + 79, /* decomodel */ 82, /* divemode */ }; @@ -472,6 +484,8 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; + unsigned int salinity = data[layout->salinity]; if (version == 0x23 || version == 0x24) salinity += 100; @@ -572,6 +586,70 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned return DC_STATUS_UNSUPPORTED; } break; + case DC_FIELD_DECOMODEL: + if (version == 0x21) { + switch (data[layout->divemode]) { + case OSTC_APNEA: + case OSTC_GAUGE: + decomodel->type = DC_DECOMODEL_NONE; + break; + case OSTC_ZHL16_OC: + case OSTC_ZHL16_CC: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = 100; + decomodel->params.gf.high = 100; + break; + case OSTC_ZHL16_OC_GF: + case OSTC_ZHL16_CC_GF: + case OSTC_PSCR_GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else if (version == 0x22) { + switch (data[layout->divemode]) { + case FROG_ZHL16: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = 100; + decomodel->params.gf.high = 100; + break; + case FROG_ZHL16_GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + break; + case FROG_APNEA: + decomodel->type = DC_DECOMODEL_NONE; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else if (version == 0x23 || version == 0x24) { + switch (data[layout->decomodel]) { + case OSTC3_ZHL16: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = 100; + decomodel->params.gf.high = 100; + break; + case OSTC3_ZHL16_GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + break; + case OSTC4_VPM: + decomodel->type = DC_DECOMODEL_VPM; + break; + default: + return DC_STATUS_DATAFORMAT; + } + } else { + return DC_STATUS_UNSUPPORTED; + } + decomodel->conservatism = 0; + break; default: return DC_STATUS_UNSUPPORTED; } diff --git a/src/liquivision_lynx_parser.c b/src/liquivision_lynx_parser.c index c4272d8..2ff52cb 100644 --- a/src/liquivision_lynx_parser.c +++ b/src/liquivision_lynx_parser.c @@ -66,6 +66,9 @@ #define TEC 2 #define REC 3 +#define ZHL16GF 0 +#define RGBM 1 + #define NORMAL 0 #define BOOKMARK 1 #define ALARM_DEPTH 2 @@ -231,6 +234,7 @@ liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -288,6 +292,22 @@ liquivision_lynx_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, } } break; + case DC_FIELD_DECOMODEL: + switch (abstract->data[93]) { + case ZHL16GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = 0; + decomodel->params.gf.high = 0; + break; + case RGBM: + decomodel->type = DC_DECOMODEL_RGBM; + decomodel->conservatism = 0; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = parser->ngasmixes; break; diff --git a/src/seac_screen_parser.c b/src/seac_screen_parser.c index 28988e4..46e3303 100644 --- a/src/seac_screen_parser.c +++ b/src/seac_screen_parser.c @@ -44,6 +44,8 @@ struct seac_screen_parser_t { unsigned int cached; unsigned int ngasmixes; unsigned int oxygen[NGASMIXES]; + unsigned int gf_low; + unsigned int gf_high; }; static dc_status_t seac_screen_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -82,6 +84,8 @@ seac_screen_parser_create (dc_parser_t **out, dc_context_t *context) for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; } + parser->gf_low = 0; + parser->gf_high = 0; *out = (dc_parser_t *) parser; @@ -99,6 +103,8 @@ seac_screen_parser_set_data (dc_parser_t *abstract, const unsigned char *data, u for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; } + parser->gf_low = 0; + parser->gf_high = 0; return DC_STATUS_SUCCESS; } @@ -207,6 +213,7 @@ seac_screen_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsig } dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -249,6 +256,12 @@ seac_screen_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsig return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = parser->gf_low; + decomodel->params.gf.high = parser->gf_high; + break; default: return DC_STATUS_UNSUPPORTED; } @@ -279,6 +292,9 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int oxygen[NGASMIXES] = {0}; unsigned int o2_previous = INVALID; + unsigned int gf_low = 0; + unsigned int gf_high = 0; + unsigned int time = 0; unsigned int offset = SZ_HEADER; while (offset + SZ_SAMPLE <= size) { @@ -298,6 +314,8 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t unsigned int decotime = array_uint16_le (data + offset + 0x10); unsigned int ndl_tts = array_uint16_le (data + offset + 0x12); unsigned int cns = array_uint16_le (data + offset + 0x16); + unsigned int gf_hi = data[offset + 0x3B]; + unsigned int gf_lo = data[offset + 0x3C]; if (id != dive_id) { ERROR (abstract->context, "Unexpected sample id (%u %u).", dive_id, id); @@ -362,6 +380,12 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t sample.cns = cns / 100.0; if (callback) callback (DC_SAMPLE_CNS, sample, userdata); + // Deco model + if (gf_low == 0 && gf_high == 0) { + gf_low = gf_lo; + gf_high = gf_hi; + } + offset += SZ_SAMPLE; } @@ -370,6 +394,8 @@ seac_screen_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t parser->oxygen[i] = oxygen[i]; } parser->ngasmixes = ngasmixes; + parser->gf_low = gf_low; + parser->gf_high = gf_high; parser->cached = 1; return DC_STATUS_SUCCESS; diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index 28c4915..5113f75 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -78,6 +78,11 @@ #define M_OC_REC 6 #define M_FREEDIVE 7 +#define GF 0 +#define VPMB 1 +#define VPMB_GFS 2 +#define DCIEM 3 + #define METRIC 0 #define IMPERIAL 1 @@ -669,9 +674,13 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ if (rc != DC_STATUS_SUCCESS) return rc; + unsigned int decomodel_idx = parser->pnf ? parser->opening[2] + 18 : 67; + unsigned int gf_idx = parser->pnf ? parser->opening[0] + 4 : 4; + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -742,6 +751,27 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + switch (data[decomodel_idx]) { + case GF: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + decomodel->params.gf.low = data[gf_idx + 0]; + decomodel->params.gf.high = data[gf_idx + 1]; + break; + case VPMB: + case VPMB_GFS: + decomodel->type = DC_DECOMODEL_VPM; + decomodel->conservatism = data[decomodel_idx + 1]; + break; + case DCIEM: + decomodel->type = DC_DECOMODEL_DCIEM; + decomodel->conservatism = 0; + break; + default: + return DC_STATUS_DATAFORMAT; + } + break; default: return DC_STATUS_UNSUPPORTED; } diff --git a/src/suunto_d9_parser.c b/src/suunto_d9_parser.c index 7fc05fd..32e28ea 100644 --- a/src/suunto_d9_parser.c +++ b/src/suunto_d9_parser.c @@ -361,6 +361,7 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne return rc; dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; if (value) { switch (type) { @@ -408,6 +409,20 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_RGBM; + if (parser->model == D4i ||parser->model == D6i || + parser->model == D9tx || parser->model == ZOOPNOVO_A || + parser->model == ZOOPNOVO_B || parser->model == VYPERNOVO || + parser->model == D4F) + decomodel->conservatism = data[0x21] - 2; + else if (parser->model == HELO2) + decomodel->conservatism = data[0x23] - 2; + else if (parser->model == DX) + decomodel->conservatism = data[0x25] - 2; + else + decomodel->conservatism = data[0x1E]; + break; default: return DC_STATUS_UNSUPPORTED; } diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index de00001..cc9276a 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -90,6 +90,7 @@ typedef struct suunto_eonsteel_parser_t { dc_tankvolume_t tankinfo[MAXGASES]; double tanksize[MAXGASES]; double tankworkingpressure[MAXGASES]; + dc_decomodel_t decomodel; } cache; } suunto_eonsteel_parser_t; @@ -1095,6 +1096,9 @@ suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsi tank->type = DC_TANKVOLUME_IMPERIAL; } break; + case DC_FIELD_DECOMODEL: + field_value(value, eon->cache.decomodel); + break; default: return DC_STATUS_UNSUPPORTED; } @@ -1341,6 +1345,20 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty return 0; } + if (!strcmp(name, "Algorithm")) { + if (!strcmp((const char *)data, "Suunto Fused RGBM")) { + eon->cache.decomodel.type = DC_DECOMODEL_RGBM; + eon->cache.initialized |= 1 << DC_FIELD_DECOMODEL; + } + return 0; + } + + if (!strcmp(name, "Conservatism")) { + eon->cache.decomodel.conservatism = *(const signed char *)data; + eon->cache.initialized |= 1 << DC_FIELD_DECOMODEL; + return 0; + } + if (!strcmp(name, "LowSetPoint")) { unsigned int pressure = array_uint32_le(data); // in SI units - Pascal eon->cache.lowsetpoint = pressure / 100000.0; // bar diff --git a/src/suunto_vyper_parser.c b/src/suunto_vyper_parser.c index 369ad5c..d7af05c 100644 --- a/src/suunto_vyper_parser.c +++ b/src/suunto_vyper_parser.c @@ -238,6 +238,7 @@ suunto_vyper_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi dc_gasmix_t *gas = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; // Cache the data. dc_status_t rc = suunto_vyper_parser_cache (parser); @@ -297,6 +298,10 @@ suunto_vyper_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi *((dc_divemode_t *) value) = DC_DIVEMODE_OC; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_RGBM; + decomodel->conservatism = (data[4] & 0x0F) / 3; + break; default: return DC_STATUS_UNSUPPORTED; }