From decfa24f92d14810c86aaa962f782aeed530b27b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 19 Aug 2016 08:23:47 +0200 Subject: [PATCH 01/19] Fix the gas mix parsing for the Aladin Tec 2G. Unlike the other models, the Aladin Tec 2G uses only a single byte to store the oxygen percentage, and there is no need to manually re-map the deco mix. Normally, the oxygen percentage is stored using two bytes (little endian byte order). Thus for a device supporting two gas mixes, four bytes will be used, and the corresponding gas mix id for each byte is as follows: ID: 0 0 1 1 After re-mapping the id of the deco mix, this becomes: ID: 0 0 2 2 Since oxygen percentages are limited to the range 0-100%, the highest byte (marked with an X) should always be zero and can thus be ignored: ID: 0 X 2 X Now, because an oxygen percentage of zero indicates a disabled gas mix, this is equivalent to a device supporting three (or even four) gas mixes, each stored using only a single byte: ID: 0 1 2 3 We can take advantage of this knowledge to avoid having to re-map the deco mix id. --- src/uwatec_smart_parser.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/uwatec_smart_parser.c b/src/uwatec_smart_parser.c index feb4844..5b0453f 100644 --- a/src/uwatec_smart_parser.c +++ b/src/uwatec_smart_parser.c @@ -211,7 +211,7 @@ static const uwatec_smart_header_info_t uwatec_smart_aladin_tec2g_header = { 22, 26, - 34, 2, + 34, 3, 30, /* temp_minimum */ 28, /* temp_maximum */ 32, /* temp_surface */ @@ -483,17 +483,17 @@ uwatec_smart_parser_cache (uwatec_smart_parser_t *parser) uwatec_smart_gasmix_t gasmix[NGASMIXES] = {{0}}; if (!trimix) { for (unsigned int i = 0; i < header->ngases; ++i) { - unsigned int id = i; - if (id > 0 && header->ngases == 2) { - id++; // Remap the id of the deco mix. + unsigned int idx = DC_GASMIX_UNKNOWN; + unsigned int o2 = 0; + if (parser->model == ALADINTEC2G) { + o2 = data[header->gasmix + i]; + } else { + o2 = array_uint16_le (data + header->gasmix + i * 2); } - unsigned int idx = DC_GASMIX_UNKNOWN; - - unsigned int o2 = data[header->gasmix + i * 2]; if (o2 != 0) { idx = ngasmixes; - gasmix[ngasmixes].id = id; + gasmix[ngasmixes].id = i; gasmix[ngasmixes].oxygen = o2; gasmix[ngasmixes].helium = 0; ngasmixes++; @@ -517,7 +517,7 @@ uwatec_smart_parser_cache (uwatec_smart_parser_t *parser) } if ((beginpressure != 0 || endpressure != 0) && (beginpressure != 0xFFFF) && (endpressure != 0xFFFF)) { - tank[ntanks].id = id; + tank[ntanks].id = i; tank[ntanks].beginpressure = beginpressure; tank[ntanks].endpressure = endpressure; tank[ntanks].gasmix = idx; From eb61dc4c75e72839520f63854e840c705bb6b1a0 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Tue, 13 Oct 2015 19:21:56 -0700 Subject: [PATCH 02/19] Add tank size reporting for Suunto EON Steel Sadly the data we get from the EON Steel is a bit of a mess. It doesn't really tell us if the data is metric or imperial (it always sends both wet volume and working pressure). And in imperial mode the Suunto engineers seem a bit confused. The pressure given (entered on the dive computer in psi) is sent to us not in bar, not in atm... it's "something". As far as I can tell it's a constant factor of 1.00069182389937 different from bar. And the wet sizes are a bit to small to get the cuft size the user entered. But instead of trying to guess and fix the mess, we just pass it through... So this is somewhat useful, but not really what most users will want. Linus started this commit with a few lines that parsed the right values out of the data stream from the Suunto EON Steel. I then added the implementation of the infrastructure to convert the raw data and report it back to the caller. Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 82 +++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 19fbf99..b64400e 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -82,6 +83,9 @@ typedef struct suunto_eonsteel_parser_t { dc_gasmix_t gasmix[MAXGASES]; dc_salinity_t salinity; double surface_pressure; + dc_tankvolume_t tankinfo[MAXGASES]; + double tanksize[MAXGASES]; + double tankworkingpressure[MAXGASES]; } cache; } suunto_eonsteel_parser_t; @@ -879,6 +883,8 @@ suunto_eonsteel_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback static dc_status_t suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value) { + dc_tank_t *tank = (dc_tank_t *) value; + suunto_eonsteel_parser_t *eon = (suunto_eonsteel_parser_t *)parser; if (!(eon->cache.initialized >> type)) @@ -895,6 +901,7 @@ suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsi field_value(value, eon->cache.avgdepth); break; case DC_FIELD_GASMIX_COUNT: + case DC_FIELD_TANK_COUNT: field_value(value, eon->cache.ngases); break; case DC_FIELD_GASMIX: @@ -908,6 +915,35 @@ suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsi case DC_FIELD_ATMOSPHERIC: field_value(value, eon->cache.surface_pressure); break; + case DC_FIELD_TANK: + /* + * Sadly it seems that the EON Steel doesn't tell us whether + * we get imperial or metric data - the only indication is + * that metric is (at least so far) always whole liters + */ + tank->volume = eon->cache.tanksize[flags]; + + /* + * The pressure reported is NOT the pressure the user enters. + * + * So 3000psi turns into 206.700 bar instead of 206.843 bar; + * We report it as we get it and let the application figure out + * what to do with that + */ + tank->workpressure = eon->cache.tankworkingpressure[flags]; + tank->type = eon->cache.tankinfo[flags]; + + /* + * See if we should call this imperial instead. + * + * We need to have workpressure and a valid tank. In that case, + * a fractional tank size implies imperial. + */ + if (tank->workpressure && (tank->type == DC_TANKVOLUME_METRIC)) { + if (fabs(tank->volume - rint(tank->volume)) > 0.001) + tank->type = DC_TANKVOLUME_IMPERIAL; + } + break; default: return DC_STATUS_UNSUPPORTED; } @@ -953,10 +989,30 @@ static void set_depth_field(suunto_eonsteel_parser_t *eon, unsigned short d) // Two versions so far: // "enum:0=Off,1=Primary,2=?,3=Diluent" // "enum:0=Off,1=Primary,3=Diluent,4=Oxygen" +// +// We turn that into the DC_TANKVOLUME data here, but +// initially consider all non-off tanks to me METRIC. +// +// We may later turn the METRIC tank size into IMPERIAL if we +// get a working pressure and non-integral size static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, unsigned char type) { - if (eon->cache.ngases < MAXGASES) - eon->cache.ngases++; + int idx = eon->cache.ngases; + dc_tankvolume_t tankinfo = DC_TANKVOLUME_METRIC; + + if (idx >= MAXGASES) + return 0; + + eon->cache.ngases = idx+1; + switch (type) { + case 0: + tankinfo = 0; + break; + default: + break; + } + eon->cache.tankinfo[idx] = tankinfo; + eon->cache.initialized |= 1 << DC_FIELD_GASMIX_COUNT; return 0; } @@ -983,6 +1039,22 @@ static int add_gas_he(suunto_eonsteel_parser_t *eon, unsigned char he) return 0; } +static int add_gas_size(suunto_eonsteel_parser_t *eon, float l) +{ + int idx = eon->cache.ngases-1; + if (idx >= 0) + eon->cache.tanksize[idx] = l; + return 0; +} + +static int add_gas_workpressure(suunto_eonsteel_parser_t *eon, float wp) +{ + int idx = eon->cache.ngases-1; + if (idx >= 0) + eon->cache.tankworkingpressure[idx] = wp; + return 0; +} + static float get_le32_float(const unsigned char *src) { union { @@ -1066,6 +1138,12 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty if (!strcmp(name, "Gases.Gas.Helium")) return add_gas_he(eon, data[0]); + if (!strcmp(name, "Gases.Gas.TankSize")) + return add_gas_size(eon, get_le32_float(data)); + + if (!strcmp(name, "Gases.Gas.TankFillPressure")) + return add_gas_workpressure(eon, get_le32_float(data)); + if (!strcmp(name, "SurfacePressure")) { unsigned int pressure = array_uint32_le(data); // in SI units - Pascal eon->cache.surface_pressure = pressure / 100000.0; // bar From 84111fe6061591ae023e5fcb95a9c687a28f589a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 20 Jun 2016 22:04:27 -0700 Subject: [PATCH 03/19] Suunto EON Steel: add descriptor debugging output .. every time I look for a new feature I add debug code to print out all the descriptors. So let's just do it once and for all. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index b64400e..3fdaaaa 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -95,6 +95,7 @@ static const struct { const char *name; enum eon_sample type; } type_translation[] = { + { "+Time", ES_dtime }, { "Depth", ES_depth }, { "Temperature", ES_temp }, { "NoDecTime", ES_ndl }, @@ -152,6 +153,16 @@ static enum eon_sample lookup_descriptor_type(suunto_eonsteel_parser_t *eon, str return ES_none; } +static const char *desc_type_name(enum eon_sample type) +{ + int i; + for (i = 0; i < C_ARRAY_SIZE(type_translation); i++) { + if (type == type_translation[i].type) + return type_translation[i].name; + } + return "Unknown"; +} + static int lookup_descriptor_size(suunto_eonsteel_parser_t *eon, struct type_desc *desc) { const char *format = desc->format; @@ -1252,6 +1263,31 @@ static void initialize_field_caches(suunto_eonsteel_parser_t *eon) eon->cache.divetime /= 1000; } +static void show_descriptor(suunto_eonsteel_parser_t *eon, int nr, struct type_desc *desc) +{ + int i; + + if (!desc->desc) + return; + DEBUG(eon->base.context, "Descriptor %d: '%s', size %d bytes", nr, desc->desc, desc->size); + if (desc->format) + DEBUG(eon->base.context, " format '%s'", desc->format); + if (desc->mod) + DEBUG(eon->base.context, " mod '%s'", desc->mod); + for (i = 0; i < EON_MAX_GROUP; i++) { + enum eon_sample type = desc->type[i]; + if (!type) + continue; + DEBUG(eon->base.context, " %d: %d (%s)", i, type, desc_type_name(type)); + } +} + +static void show_all_descriptors(suunto_eonsteel_parser_t *eon) +{ + for (unsigned int i = 0; i < MAXTYPE; ++i) + show_descriptor(eon, i, eon->type_desc+i); +} + static dc_status_t suunto_eonsteel_parser_set_data(dc_parser_t *parser, const unsigned char *data, unsigned int size) { @@ -1260,6 +1296,7 @@ suunto_eonsteel_parser_set_data(dc_parser_t *parser, const unsigned char *data, desc_free(eon->type_desc, MAXTYPE); memset(eon->type_desc, 0, sizeof(eon->type_desc)); initialize_field_caches(eon); + show_all_descriptors(eon); return DC_STATUS_SUCCESS; } From c6b681a75398adfee04ffecec15a01002f782aec Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 20 Jun 2016 22:51:52 -0700 Subject: [PATCH 04/19] Initial Suunto EON Steel CCR support This does the basic divemode and setpoint parsing for the EON Steel, and gets the CCR download right in the big picture. The cylinder information is still confusing and incorrect, but this is a big step in the right direction. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 124 +++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 19 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 3fdaaaa..1d7e9e1 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -35,27 +35,30 @@ enum eon_sample { ES_none = 0, - ES_dtime, // duint16,precision=3 (time delta in ms) - ES_depth, // uint16,precision=2,nillable=65535 (depth in cm) - ES_temp, // int16,precision=2,nillable=-3000 (temp in deci-Celsius) - ES_ndl, // int16,nillable=-1 (ndl in minutes) - ES_ceiling, // uint16,precision=2,nillable=65535 (ceiling in cm) - ES_tts, // uint16,nillable=65535 (time to surface) - ES_heading, // uint16,precision=4,nillable=65535 (heading in degrees) - ES_abspressure, // uint16,precision=0,nillable=65535 (abs presure in centibar) - ES_gastime, // int16,nillable=-1 (remaining gas time in minutes) - ES_ventilation, // uint16,precision=6,nillable=65535 ("x/6000000,x"? No idea) - ES_gasnr, // uint8 - ES_pressure, // uint16,nillable=65535 (cylinder pressure in centibar) - ES_state, - ES_state_active, - ES_notify, - ES_notify_active, - ES_warning, - ES_warning_active, + ES_dtime, // duint16,precision=3 (time delta in ms) + ES_depth, // uint16,precision=2,nillable=65535 (depth in cm) + ES_temp, // int16,precision=2,nillable=-3000 (temp in deci-Celsius) + ES_ndl, // int16,nillable=-1 (ndl in minutes) + ES_ceiling, // uint16,precision=2,nillable=65535 (ceiling in cm) + ES_tts, // uint16,nillable=65535 (time to surface) + ES_heading, // uint16,precision=4,nillable=65535 (heading in degrees) + ES_abspressure, // uint16,precision=0,nillable=65535 (abs presure in centibar) + ES_gastime, // int16,nillable=-1 (remaining gas time in minutes) + ES_ventilation, // uint16,precision=6,nillable=65535 ("x/6000000,x"? No idea) + ES_gasnr, // uint8 + ES_pressure, // uint16,nillable=65535 (cylinder pressure in centibar) + ES_state, // enum:0=Wet Outside,1=Below Wet Activation Depth,2=Below Surface,3=Dive Active,4=Surface Calculation,5=Tank pressure available,6=Closed Circuit Mode + ES_state_active, // bool + ES_notify, // enum:0=NoFly Time,1=Depth,2=Surface Time,3=Tissue Level,4=Deco,5=Deco Window,6=Safety Stop Ahead,7=Safety Stop,8=Safety Stop Broken,9=Deep Stop Ahead,10=Deep Stop,11=Dive Time,12=Gas Available,13=SetPoint Switch,14=Diluent Hypoxia,15=Air Time,16=Tank Pressure + ES_notify_active, // bool + ES_warning, // enum:0=ICD Penalty,1=Deep Stop Penalty,2=Mandatory Safety Stop,3=OTU250,4=OTU300,5=CNS80%,6=CNS100%,7=Max.Depth,8=Air Time,9=Tank Pressure,10=Safety Stop Broken,11=Deep Stop Broken,12=Ceiling Broken,13=PO2 High + ES_warning_active, // bool ES_alarm, ES_alarm_active, - ES_gasswitch, // uint16 + ES_gasswitch, // uint16 + ES_setpoint_type, // enum:0=Low,1=High,2=Custom + ES_setpoint_po2, // uint32 + ES_setpoint_automatic, // bool ES_bookmark, }; @@ -83,6 +86,10 @@ typedef struct suunto_eonsteel_parser_t { dc_gasmix_t gasmix[MAXGASES]; dc_salinity_t salinity; double surface_pressure; + dc_divemode_t divemode; + double lowsetpoint; + double highsetpoint; + double customsetpoint; dc_tankvolume_t tankinfo[MAXGASES]; double tanksize[MAXGASES]; double tankworkingpressure[MAXGASES]; @@ -117,6 +124,9 @@ static const struct { { "Events.Alarm.Active", ES_alarm_active }, { "Events.Bookmark.Name", ES_bookmark }, { "Events.GasSwitch.GasNumber", ES_gasswitch }, + { "Events.SetPoint.Type", ES_setpoint_type }, + { "Events.Events.SetPoint.PO2", ES_setpoint_po2 }, + { "Events.SetPoint.Automatic", ES_setpoint_automatic }, { "Events.DiveTimer.Active", ES_none }, { "Events.DiveTimer.Time", ES_none }, }; @@ -737,6 +747,41 @@ static void sample_event_alarm_value(struct sample_data *info, unsigned char val if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); } +// enum:0=Low,1=High,2=Custom +static void sample_setpoint_type(struct sample_data *info, unsigned char value) +{ + dc_sample_value_t sample = {0}; + + switch (value) { + case 0: + sample.ppo2 = info->eon->cache.lowsetpoint; + break; + case 1: + sample.ppo2 = info->eon->cache.highsetpoint; + break; + case 2: + sample.ppo2 = info->eon->cache.customsetpoint; + break; + default: + DEBUG(info->eon->base.context, "sample_setpoint_type(%u)", value); + return; + } + if (info->callback) info->callback(DC_SAMPLE_SETPOINT, sample, info->userdata); +} + +// uint32 +static void sample_setpoint_po2(struct sample_data *info, unsigned int pressure) +{ + // I *think* this just sets the custom SP, and then + // we'll get a setpoint_type(2) later. + info->eon->cache.customsetpoint = pressure / 100000.0; // Pascal to bar +} + +static void sample_setpoint_automatic(struct sample_data *info, unsigned char value) +{ + DEBUG(info->eon->base.context, "sample_setpoint_automatic(%u)", value); +} + static int handle_sample_type(struct sample_data *info, enum eon_sample type, const unsigned char *data) { switch (type) { @@ -828,6 +873,18 @@ static int handle_sample_type(struct sample_data *info, enum eon_sample type, co sample_gas_switch_event(info, array_uint16_le(data)); return 2; + case ES_setpoint_type: + sample_setpoint_type(info, data[0]); + return 1; + + case ES_setpoint_po2: + sample_setpoint_po2(info, array_uint32_le(data)); + return 4; + + case ES_setpoint_automatic: // bool + sample_setpoint_automatic(info, data[0]); + return 1; + default: return 0; } @@ -926,6 +983,9 @@ suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsi case DC_FIELD_ATMOSPHERIC: field_value(value, eon->cache.surface_pressure); break; + case DC_FIELD_DIVEMODE: + field_value(value, eon->cache.divemode); + break; case DC_FIELD_TANK: /* * Sadly it seems that the EON Steel doesn't tell us whether @@ -1115,6 +1175,12 @@ static int traverse_device_fields(suunto_eonsteel_parser_t *eon, const struct ty // AlgorithmTransitionDepth (uint8) // DaysInSeries (uint32) // PreviousDiveDepth (float32,precision=2) +// LowSetPoint (uint32) +// HighSetPoint (uint32) +// SwitchHighSetPoint.Enabled (bool) +// SwitchHighSetPoint.Depth (float32,precision=1) +// SwitchLowSetPoint.Enabled (bool) +// SwitchLowSetPoint.Depth (float32,precision=1) // StartTissue.CNS (float32,precision=3) // StartTissue.OTU (float32) // StartTissue.OLF (float32,precision=3) @@ -1162,6 +1228,26 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty return 0; } + if (!strcmp(name, "DiveMode")) { + if (!strncmp(data, "CCR", 3)) { + eon->cache.divemode = DC_DIVEMODE_CC; + eon->cache.initialized |= 1 << DC_FIELD_DIVEMODE; + } + return 0; + } + + if (!strcmp(name, "LowSetPoint")) { + unsigned int pressure = array_uint32_le(data); // in SI units - Pascal + eon->cache.lowsetpoint = pressure / 100000.0; // bar + return 0; + } + + if (!strcmp(name, "HighSetPoint")) { + unsigned int pressure = array_uint32_le(data); // in SI units - Pascal + eon->cache.highsetpoint = pressure / 100000.0; // bar + return 0; + } + return 0; } From 42601825aa24ff75dee8796b5ad7d4a7e8ba4d1a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 16 Jul 2016 15:39:19 +0900 Subject: [PATCH 05/19] EON Steel: fix uninitialized field cache entries The check for whether we had initialized a field in the EON Steel cache data structure was wrong, causing some entries to be returned successfully even if their field had never been initialized. In most cases, it didn't matter, since the cache data was initialized to zero (which is a fine default for uninitialized data), so most of the time it didn't matter. But for the recently added dive mode field, this caused OC dives to be returned as freedives, for example. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 1d7e9e1..8d213bc 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -955,7 +955,7 @@ suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsi suunto_eonsteel_parser_t *eon = (suunto_eonsteel_parser_t *)parser; - if (!(eon->cache.initialized >> type)) + if (!(eon->cache.initialized & (1 << type))) return DC_STATUS_UNSUPPORTED; switch (type) { From 8ade60304de874c2456ab808a7d25a84311b84bd Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 17 Jul 2016 09:08:04 +0900 Subject: [PATCH 06/19] EON Steel: mark tank cache initialized when filling it in The previous commit fixed the cache initialization testing, and uncovered the fact that the tank size cache initialization didn't set the initialized bit correctly. That oversight had been hidden by the fact that we then tested the bit wrongly, so not setting it right didn't use to matter as long as there were other higher cache bits that were set. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 8d213bc..ee90b4f 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -1085,6 +1085,7 @@ static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *d eon->cache.tankinfo[idx] = tankinfo; eon->cache.initialized |= 1 << DC_FIELD_GASMIX_COUNT; + eon->cache.initialized |= 1 << DC_FIELD_TANK_COUNT; return 0; } @@ -1115,6 +1116,7 @@ static int add_gas_size(suunto_eonsteel_parser_t *eon, float l) int idx = eon->cache.ngases-1; if (idx >= 0) eon->cache.tanksize[idx] = l; + eon->cache.initialized |= 1 << DC_FIELD_TANK; return 0; } From a1947f3fb0f937cd31cd4dcb6c9c12a84e5755d8 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 20 Jul 2016 11:58:00 +0900 Subject: [PATCH 07/19] EON Steel: pass in the type descriptor to samples that need it The samples that take 'enum' types need the type descriptor to parse what the enum type means. This doesn't actually use the data yet, I need to add parsing of the enum descriptor string. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 40 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index ee90b4f..368aa47 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -619,12 +619,12 @@ static void sample_gas_switch_event(struct sample_data *info, unsigned short idx * * FIXME! This needs to parse the actual type descriptor enum */ -static void sample_event_state_type(struct sample_data *info, unsigned char type) +static void sample_event_state_type(const struct type_desc *desc, struct sample_data *info, unsigned char type) { info->state_type = type; } -static void sample_event_state_value(struct sample_data *info, unsigned char value) +static void sample_event_state_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { /* * We could turn these into sample events, but they don't actually @@ -638,14 +638,14 @@ static void sample_event_state_value(struct sample_data *info, unsigned char val */ } -static void sample_event_notify_type(struct sample_data *info, unsigned char type) +static void sample_event_notify_type(const struct type_desc *desc, struct sample_data *info, unsigned char type) { info->notify_type = type; } // FIXME! This needs to parse the actual type descriptor enum -static void sample_event_notify_value(struct sample_data *info, unsigned char value) +static void sample_event_notify_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; static const enum parser_sample_event_t translate_notification[] = { @@ -679,13 +679,13 @@ static void sample_event_notify_value(struct sample_data *info, unsigned char va } -static void sample_event_warning_type(struct sample_data *info, unsigned char type) +static void sample_event_warning_type(const struct type_desc *desc, struct sample_data *info, unsigned char type) { info->warning_type = type; } -static void sample_event_warning_value(struct sample_data *info, unsigned char value) +static void sample_event_warning_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; static const enum parser_sample_event_t translate_warning[] = { @@ -716,14 +716,14 @@ static void sample_event_warning_value(struct sample_data *info, unsigned char v if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); } -static void sample_event_alarm_type(struct sample_data *info, unsigned char type) +static void sample_event_alarm_type(const struct type_desc *desc, struct sample_data *info, unsigned char type) { info->alarm_type = type; } // FIXME! This needs to parse the actual type descriptor enum -static void sample_event_alarm_value(struct sample_data *info, unsigned char value) +static void sample_event_alarm_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; static const enum parser_sample_event_t translate_alarm[] = { @@ -748,7 +748,7 @@ static void sample_event_alarm_value(struct sample_data *info, unsigned char val } // enum:0=Low,1=High,2=Custom -static void sample_setpoint_type(struct sample_data *info, unsigned char value) +static void sample_setpoint_type(const struct type_desc *desc, struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; @@ -782,7 +782,7 @@ static void sample_setpoint_automatic(struct sample_data *info, unsigned char va DEBUG(info->eon->base.context, "sample_setpoint_automatic(%u)", value); } -static int handle_sample_type(struct sample_data *info, enum eon_sample type, const unsigned char *data) +static int handle_sample_type(const struct type_desc *desc, struct sample_data *info, enum eon_sample type, const unsigned char *data) { switch (type) { case ES_dtime: @@ -834,35 +834,35 @@ static int handle_sample_type(struct sample_data *info, enum eon_sample type, co return 2; case ES_state: - sample_event_state_type(info, data[0]); + sample_event_state_type(desc, info, data[0]); return 1; case ES_state_active: - sample_event_state_value(info, data[0]); + sample_event_state_value(desc, info, data[0]); return 1; case ES_notify: - sample_event_notify_type(info, data[0]); + sample_event_notify_type(desc, info, data[0]); return 1; case ES_notify_active: - sample_event_notify_value(info, data[0]); + sample_event_notify_value(desc, info, data[0]); return 1; case ES_warning: - sample_event_warning_type(info, data[0]); + sample_event_warning_type(desc, info, data[0]); return 1; case ES_warning_active: - sample_event_warning_value(info, data[0]); + sample_event_warning_value(desc, info, data[0]); return 1; case ES_alarm: - sample_event_alarm_type(info, data[0]); + sample_event_alarm_type(desc, info, data[0]); return 1; case ES_alarm_active: - sample_event_alarm_value(info, data[0]); + sample_event_alarm_value(desc, info, data[0]); return 1; case ES_bookmark: @@ -874,7 +874,7 @@ static int handle_sample_type(struct sample_data *info, enum eon_sample type, co return 2; case ES_setpoint_type: - sample_setpoint_type(info, data[0]); + sample_setpoint_type(desc, info, data[0]); return 1; case ES_setpoint_po2: @@ -905,7 +905,7 @@ static int traverse_samples(unsigned short type, const struct type_desc *desc, c for (i = 0; i < EON_MAX_GROUP; i++) { enum eon_sample type = desc->type[i]; - int bytes = handle_sample_type(info, type, data); + int bytes = handle_sample_type(desc, info, type, data); if (!bytes) break; From 864b46603963ea2f70f5166bb7a738a12fc280fc Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 20 Jul 2016 15:12:46 +0900 Subject: [PATCH 08/19] EON Steel: look up enum descriptor strings It turns out you can't hardcode the enum numbers either, since they change from dive to dive (or possibly firmware version to firmware version). So do it right, and actually parse the string descriptor for the enum. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 179 +++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 84 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 368aa47..4590ca9 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -449,8 +449,8 @@ struct sample_data { dc_sample_callback_t callback; void *userdata; unsigned int time; - unsigned char state_type, notify_type; - unsigned char warning_type, alarm_type; + const char *state_type, *notify_type; + const char *warning_type, *alarm_type; /* We gather up deco and cylinder pressure information */ int gasnr; @@ -600,77 +600,111 @@ static void sample_gas_switch_event(struct sample_data *info, unsigned short idx #endif } +/* + * Look up the string from an enumeration. + * + * Enumerations have the enum values in the "format" string, + * and all start with "enum:" followed by a comma-separated list + * of enumeration values and strings. Example: + * + * "enum:0=NoFly Time,1=Depth,2=Surface Time,3=..." + */ +static const char *lookup_enum(const struct type_desc *desc, unsigned char value) +{ + const char *str = desc->format; + unsigned char c; + + if (!str) + return NULL; + if (strncmp(str, "enum:", 5)) + return NULL; + str += 5; + + while ((c = *str) != 0) { + unsigned char n; + const char *begin, *end; + char *ret; + + str++; + if (!isdigit(c)) + continue; + n = c - '0'; + + // We only handle one or two digits + if (isdigit(*str)) { + n = n*10 + *str - '0'; + str++; + } + + begin = end = str; + while ((c = *str) != 0) { + str++; + if (c == ',') + break; + end = str; + } + + // Verify that it has the 'n=string' format and skip the equals sign + if (*begin != '=') + continue; + begin++; + + // Is it the value we're looking for? + if (n != value) + continue; + + ret = malloc(end - begin + 1); + if (!ret) + break; + + memcpy(ret, begin, end-begin); + ret[end-begin] = 0; + return ret; + } + return NULL; +} + /* * The EON Steel has four different sample events: "state", "notification", * "warning" and "alarm". All end up having two fields: type and a boolean value. - * - * The type enumerations are available as part of the type descriptor, and we - * *should* probably parse them dynamically, but this hardcodes the different - * type values. - * - * For event states, the types are: - * - * 0=Wet Outside - * 1=Below Wet Activation Depth - * 2=Below Surface - * 3=Dive Active - * 4=Surface Calculation - * 5=Tank pressure available - * - * FIXME! This needs to parse the actual type descriptor enum */ static void sample_event_state_type(const struct type_desc *desc, struct sample_data *info, unsigned char type) { - info->state_type = type; + info->state_type = lookup_enum(desc, type); } static void sample_event_state_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { - /* - * We could turn these into sample events, but they don't actually - * match any libdivecomputer events. - * - * unsigned int state = info->state_type; - * dc_sample_value_t sample = {0}; - * sample.event.type = ... - * sample.event.value = value; - * if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); - */ + dc_sample_value_t sample = {0}; + const char *name; + + name = info->state_type; + if (!name) + return; + + sample.event.type = SAMPLE_EVENT_NONE; + if (sample.event.type == SAMPLE_EVENT_NONE) + return; + + sample.event.value = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; + if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); } static void sample_event_notify_type(const struct type_desc *desc, struct sample_data *info, unsigned char type) { - info->notify_type = type; + info->notify_type = lookup_enum(desc, type); } - -// FIXME! This needs to parse the actual type descriptor enum static void sample_event_notify_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; - static const enum parser_sample_event_t translate_notification[] = { - SAMPLE_EVENT_NONE, // 0=NoFly Time - SAMPLE_EVENT_NONE, // 1=Depth - SAMPLE_EVENT_NONE, // 2=Surface Time - SAMPLE_EVENT_TISSUELEVEL, // 3=Tissue Level - SAMPLE_EVENT_NONE, // 4=Deco - SAMPLE_EVENT_NONE, // 5=Deco Window - SAMPLE_EVENT_SAFETYSTOP_VOLUNTARY, // 6=Safety Stop Ahead - SAMPLE_EVENT_SAFETYSTOP, // 7=Safety Stop - SAMPLE_EVENT_CEILING_SAFETYSTOP, // 8=Safety Stop Broken - SAMPLE_EVENT_NONE, // 9=Deep Stop Ahead - SAMPLE_EVENT_DEEPSTOP, // 10=Deep Stop - SAMPLE_EVENT_DIVETIME, // 11=Dive Time - SAMPLE_EVENT_NONE, // 12=Gas Available - SAMPLE_EVENT_NONE, // 13=SetPoint Switch - SAMPLE_EVENT_NONE, // 14=Diluent Hypoxia - SAMPLE_EVENT_NONE, // 15=Tank Pressure - }; + const char *name; - if (info->notify_type > 15) + name = info->notify_type; + if (!name) return; - sample.event.type = translate_notification[info->notify_type]; + sample.event.type = SAMPLE_EVENT_NONE; if (sample.event.type == SAMPLE_EVENT_NONE) return; @@ -681,34 +715,19 @@ static void sample_event_notify_value(const struct type_desc *desc, struct sampl static void sample_event_warning_type(const struct type_desc *desc, struct sample_data *info, unsigned char type) { - info->warning_type = type; + info->warning_type = lookup_enum(desc, type); } - static void sample_event_warning_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; - static const enum parser_sample_event_t translate_warning[] = { - SAMPLE_EVENT_NONE, // 0=ICD Penalty ("Isobaric counterdiffusion") - SAMPLE_EVENT_VIOLATION, // 1=Deep Stop Penalty - SAMPLE_EVENT_SAFETYSTOP_MANDATORY, // 2=Mandatory Safety Stop - SAMPLE_EVENT_NONE, // 3=OTU250 - SAMPLE_EVENT_NONE, // 4=OTU300 - SAMPLE_EVENT_NONE, // 5=CNS80% - SAMPLE_EVENT_NONE, // 6=CNS100% - SAMPLE_EVENT_AIRTIME, // 7=Air Time - SAMPLE_EVENT_MAXDEPTH, // 8=Max.Depth - SAMPLE_EVENT_AIRTIME, // 9=Tank Pressure - SAMPLE_EVENT_CEILING_SAFETYSTOP, // 10=Safety Stop Broken - SAMPLE_EVENT_CEILING_SAFETYSTOP, // 11=Deep Stop Broken - SAMPLE_EVENT_CEILING, // 12=Ceiling Broken - SAMPLE_EVENT_PO2, // 13=PO2 High - }; + const char *name; - if (info->warning_type > 13) + name = info->warning_type; + if (!name) return; - sample.event.type = translate_warning[info->warning_type]; + sample.event.type = SAMPLE_EVENT_NONE; if (sample.event.type == SAMPLE_EVENT_NONE) return; @@ -718,28 +737,20 @@ static void sample_event_warning_value(const struct type_desc *desc, struct samp static void sample_event_alarm_type(const struct type_desc *desc, struct sample_data *info, unsigned char type) { - info->alarm_type = type; + info->alarm_type = lookup_enum(desc, type); } -// FIXME! This needs to parse the actual type descriptor enum static void sample_event_alarm_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; - static const enum parser_sample_event_t translate_alarm[] = { - SAMPLE_EVENT_CEILING_SAFETYSTOP, // 0=Mandatory Safety Stop Broken - SAMPLE_EVENT_ASCENT, // 1=Ascent Speed - SAMPLE_EVENT_NONE, // 2=Diluent Hyperoxia - SAMPLE_EVENT_VIOLATION, // 3=Violated Deep Stop - SAMPLE_EVENT_CEILING, // 4=Ceiling Broken - SAMPLE_EVENT_PO2, // 5=PO2 High - SAMPLE_EVENT_PO2, // 6=PO2 Low - }; + const char *name; - if (info->alarm_type > 6) + name = info->alarm_type; + if (!name) return; - sample.event.type = translate_alarm[info->alarm_type]; + sample.event.type = SAMPLE_EVENT_NONE; if (sample.event.type == SAMPLE_EVENT_NONE) return; From 2b57b1181dc1e97918072dec7fa400c982ae26a5 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 21 Jul 2016 19:39:51 +0900 Subject: [PATCH 09/19] Suunto EON Steel: split out gas info parsing The dive gas parsing cases can be split out into a helper function to keep things more manageable. Especially since there will be a couple more cases coming up. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 64 ++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 4590ca9..99fbed9 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -1166,19 +1166,45 @@ static int traverse_device_fields(suunto_eonsteel_parser_t *eon, const struct ty return 0; } +// "sml.DeviceLog.Header.Diving.Gases" +// +// +Gas.State (enum:0=Off,1=Primary,3=Diluent,4=Oxygen) +// .Gas.Oxygen (uint8,precision=2) +// .Gas.Helium (uint8,precision=2) +// .Gas.PO2 (uint32) +// .Gas.TransmitterID (utf8) +// .Gas.TankSize (float32,precision=5) +// .Gas.TankFillPressure (float32,precision=0) +// .Gas.StartPressure (float32,precision=0) +// .Gas.EndPressure (float32,precision=0) +// .Gas.TransmitterStartBatteryCharge (int8,precision=2) +// .Gas.TransmitterEndBatteryCharge (int8,precision=2) +static int traverse_gas_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, + const unsigned char *data, int len) +{ + const char *name = desc->desc + strlen("sml.DeviceLog.Header.Diving.Gases"); + + if (!strcmp(name, "+Gas.State")) + return add_gas_type(eon, desc, data[0]); + + if (!strcmp(name, ".Gas.Oxygen")) + return add_gas_o2(eon, data[0]); + + if (!strcmp(name, ".Gas.Helium")) + return add_gas_he(eon, data[0]); + + if (!strcmp(name, ".Gas.TankSize")) + return add_gas_size(eon, get_le32_float(data)); + + if (!strcmp(name, ".Gas.TankFillPressure")) + return add_gas_workpressure(eon, get_le32_float(data)); + + return 0; +} + + // "sml.DeviceLog.Header.Diving." // -// Gases+Gas.State (enum:0=Off,1=Primary,3=Diluent,4=Oxygen) -// Gases.Gas.Oxygen (uint8,precision=2) -// Gases.Gas.Helium (uint8,precision=2) -// Gases.Gas.PO2 (uint32) -// Gases.Gas.TransmitterID (utf8) -// Gases.Gas.TankSize (float32,precision=5) -// Gases.Gas.TankFillPressure (float32,precision=0) -// Gases.Gas.StartPressure (float32,precision=0) -// Gases.Gas.EndPressure (float32,precision=0) -// Gases.Gas.TransmitterStartBatteryCharge (int8,precision=2) -// Gases.Gas.TransmitterEndBatteryCharge (int8,precision=2) // SurfaceTime (uint32) // NumberInSeries (uint32) // Algorithm (utf8) @@ -1219,20 +1245,8 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty { const char *name = desc->desc + strlen("sml.DeviceLog.Header.Diving."); - if (!strcmp(name, "Gases+Gas.State")) - return add_gas_type(eon, desc, data[0]); - - if (!strcmp(name, "Gases.Gas.Oxygen")) - return add_gas_o2(eon, data[0]); - - if (!strcmp(name, "Gases.Gas.Helium")) - return add_gas_he(eon, data[0]); - - if (!strcmp(name, "Gases.Gas.TankSize")) - return add_gas_size(eon, get_le32_float(data)); - - if (!strcmp(name, "Gases.Gas.TankFillPressure")) - return add_gas_workpressure(eon, get_le32_float(data)); + if (!strncmp(name, "Gases", 5)) + return traverse_gas_fields(eon, desc, data, len); if (!strcmp(name, "SurfacePressure")) { unsigned int pressure = array_uint32_le(data); // in SI units - Pascal From 6352b90a34703be057e808696fd0e776085ea25b Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 23 Jul 2016 15:32:50 +0900 Subject: [PATCH 10/19] Suunto EON Steel: fix the event begin/end flag This fixes a bug where the begin/end marker was mistakenly added as the value instead of as flag. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 99fbed9..5e3ee7b 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -686,7 +686,7 @@ static void sample_event_state_value(const struct type_desc *desc, struct sample if (sample.event.type == SAMPLE_EVENT_NONE) return; - sample.event.value = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; + sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); } @@ -708,7 +708,7 @@ static void sample_event_notify_value(const struct type_desc *desc, struct sampl if (sample.event.type == SAMPLE_EVENT_NONE) return; - sample.event.value = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; + sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); } @@ -731,7 +731,7 @@ static void sample_event_warning_value(const struct type_desc *desc, struct samp if (sample.event.type == SAMPLE_EVENT_NONE) return; - sample.event.value = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; + sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); } @@ -754,7 +754,7 @@ static void sample_event_alarm_value(const struct type_desc *desc, struct sample if (sample.event.type == SAMPLE_EVENT_NONE) return; - sample.event.value = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; + sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); } From 40bc67d8ffc50446f9e0d81cfd7cd355dbce281e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 28 Aug 2016 12:56:09 -0700 Subject: [PATCH 11/19] Suunto EON Steel: do the proper enum lookup for a few more cases Instead of hardcoding the enum values for setpoint type and gas type, use "lookup_enum()" to actually parse the enum data and use that. I don't think this matters right now, since the numeric translations haven't changed, but it is the RigthThing(tm) to do. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 45 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 5e3ee7b..8442cca 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -762,21 +762,24 @@ static void sample_event_alarm_value(const struct type_desc *desc, struct sample static void sample_setpoint_type(const struct type_desc *desc, struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; + const char *type = lookup_enum(desc, value); - switch (value) { - case 0: - sample.ppo2 = info->eon->cache.lowsetpoint; - break; - case 1: - sample.ppo2 = info->eon->cache.highsetpoint; - break; - case 2: - sample.ppo2 = info->eon->cache.customsetpoint; - break; - default: - DEBUG(info->eon->base.context, "sample_setpoint_type(%u)", value); + if (!type) { + DEBUG(info->eon->base.context, "sample_setpoint_type(%u) did not match anything in %s", value, desc->format); return; } + + if (!strcasecmp(type, "Low")) + sample.ppo2 = info->eon->cache.lowsetpoint; + else if (!strcasecmp(type, "High")) + sample.ppo2 = info->eon->cache.highsetpoint; + else if (!strcasecmp(type, "Custom")) + sample.ppo2 = info->eon->cache.customsetpoint; + else { + DEBUG(info->eon->base.context, "sample_setpoint_type(%u) unknown type '%s'", value, type); + return; + } + if (info->callback) info->callback(DC_SAMPLE_SETPOINT, sample, info->userdata); } @@ -1081,18 +1084,24 @@ static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *d { int idx = eon->cache.ngases; dc_tankvolume_t tankinfo = DC_TANKVOLUME_METRIC; + const char *name; if (idx >= MAXGASES) return 0; eon->cache.ngases = idx+1; - switch (type) { - case 0: + name = lookup_enum(desc, type); + if (!name) + DEBUG(eon->base.context, "Unable to look up gas type %u in %s", type, desc->format); + else if (!strcasecmp(name, "Diluent")) + ; + else if (!strcasecmp(name, "Oxygen")) + ; + else if (!strcasecmp(name, "None")) tankinfo = 0; - break; - default: - break; - } + else if (strcasecmp(name, "Primary")) + DEBUG(eon->base.context, "Unknown gas type %u (%s)", type, name); + eon->cache.tankinfo[idx] = tankinfo; eon->cache.initialized |= 1 << DC_FIELD_GASMIX_COUNT; From 5e8b4dd6dccde5b57f293dd2b147d06b5d8cc6a6 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 29 Aug 2016 15:03:02 -0700 Subject: [PATCH 12/19] Suunto EON Steel: initialize the tank 'gasmix' index The gasmix query interface considers cylinders and gas mixes independent things, so the tank data structure has a pointer to the gasmix index. But the EON Steel treats cylinders as just having a gasmix (and so does subsurface, for that matter), so the gasmix index for the tank is just the same as the tank index. But we never filled it in, so you'd always see a "gas index" of zero, and subsurface would end up warning each time about how the gasmix index doesn't match the cylinder index (but because subsurface actually agreed with EON Steel, it worked despite the warning). Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 8442cca..0501a30 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -1007,6 +1007,7 @@ suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsi * that metric is (at least so far) always whole liters */ tank->volume = eon->cache.tanksize[flags]; + tank->gasmix = flags; /* * The pressure reported is NOT the pressure the user enters. From ec473feabff7b25d4d206f474209080041a4f5a1 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 7 Sep 2016 15:53:07 +0200 Subject: [PATCH 13/19] Restore the sample events. In commit 864b46603963ea2f70f5166bb7a738a12fc280fc, the sample events have been removed because we need to parse the enum string descriptor instead of the numeric value. --- src/suunto_eonsteel_parser.c | 76 ++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 0501a30..64a01ca 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -98,6 +98,11 @@ typedef struct suunto_eonsteel_parser_t { typedef int (*eon_data_cb_t)(unsigned short type, const struct type_desc *desc, const unsigned char *data, int len, void *user); +typedef struct eon_event_t { + const char *name; + parser_sample_event_t type; +} eon_event_t; + static const struct { const char *name; enum eon_sample type; @@ -163,6 +168,16 @@ static enum eon_sample lookup_descriptor_type(suunto_eonsteel_parser_t *eon, str return ES_none; } +static parser_sample_event_t lookup_event(const char *name, const eon_event_t events[], size_t n) +{ + for (size_t i = 0; i < n; ++i) { + if (!strcasecmp(name, events[i].name)) + return events[i].type; + } + + return SAMPLE_EVENT_NONE; +} + static const char *desc_type_name(enum eon_sample type) { int i; @@ -676,13 +691,22 @@ static void sample_event_state_type(const struct type_desc *desc, struct sample_ static void sample_event_state_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; + static const eon_event_t states[] = { + {"Wet Outside", SAMPLE_EVENT_NONE}, + {"Below Wet Activation Depth", SAMPLE_EVENT_NONE}, + {"Below Surface", SAMPLE_EVENT_NONE}, + {"Dive Active", SAMPLE_EVENT_NONE}, + {"Surface Calculation", SAMPLE_EVENT_NONE}, + {"Tank pressure available", SAMPLE_EVENT_NONE}, + {"Closed Circuit Mode", SAMPLE_EVENT_NONE}, + }; const char *name; name = info->state_type; if (!name) return; - sample.event.type = SAMPLE_EVENT_NONE; + sample.event.type = lookup_event(name, states, C_ARRAY_SIZE(states)); if (sample.event.type == SAMPLE_EVENT_NONE) return; @@ -697,6 +721,25 @@ static void sample_event_notify_type(const struct type_desc *desc, struct sample static void sample_event_notify_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { + static const eon_event_t notifications[] = { + {"NoFly Time", SAMPLE_EVENT_NONE}, + {"Depth", SAMPLE_EVENT_NONE}, + {"Surface Time", SAMPLE_EVENT_NONE}, + {"Tissue Level", SAMPLE_EVENT_TISSUELEVEL}, + {"Deco", SAMPLE_EVENT_NONE}, + {"Deco Window", SAMPLE_EVENT_NONE}, + {"Safety Stop Ahead", SAMPLE_EVENT_NONE}, + {"Safety Stop", SAMPLE_EVENT_SAFETYSTOP}, + {"Safety Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP}, + {"Deep Stop Ahead", SAMPLE_EVENT_NONE}, + {"Deep Stop", SAMPLE_EVENT_DEEPSTOP}, + {"Dive Time", SAMPLE_EVENT_DIVETIME}, + {"Gas Available", SAMPLE_EVENT_NONE}, + {"SetPoint Switch", SAMPLE_EVENT_NONE}, + {"Diluent Hypoxia", SAMPLE_EVENT_NONE}, + {"Air Time", SAMPLE_EVENT_NONE}, + {"Tank Pressure", SAMPLE_EVENT_NONE}, + }; dc_sample_value_t sample = {0}; const char *name; @@ -704,7 +747,7 @@ static void sample_event_notify_value(const struct type_desc *desc, struct sampl if (!name) return; - sample.event.type = SAMPLE_EVENT_NONE; + sample.event.type = lookup_event(name, notifications, C_ARRAY_SIZE(notifications)); if (sample.event.type == SAMPLE_EVENT_NONE) return; @@ -720,6 +763,22 @@ static void sample_event_warning_type(const struct type_desc *desc, struct sampl static void sample_event_warning_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { + static const eon_event_t warnings[] = { + {"ICD Penalty", SAMPLE_EVENT_NONE}, + {"Deep Stop Penalty", SAMPLE_EVENT_VIOLATION}, + {"Mandatory Safety Stop", SAMPLE_EVENT_SAFETYSTOP_MANDATORY}, + {"OTU250", SAMPLE_EVENT_NONE}, + {"OTU300", SAMPLE_EVENT_NONE}, + {"CNS80%", SAMPLE_EVENT_NONE}, + {"CNS100%", SAMPLE_EVENT_NONE}, + {"Max.Depth", SAMPLE_EVENT_MAXDEPTH}, + {"Air Time", SAMPLE_EVENT_AIRTIME}, + {"Tank Pressure", SAMPLE_EVENT_NONE}, + {"Safety Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP}, + {"Deep Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP}, + {"Ceiling Broken", SAMPLE_EVENT_CEILING}, + {"PO2 High", SAMPLE_EVENT_PO2}, + }; dc_sample_value_t sample = {0}; const char *name; @@ -727,7 +786,7 @@ static void sample_event_warning_value(const struct type_desc *desc, struct samp if (!name) return; - sample.event.type = SAMPLE_EVENT_NONE; + sample.event.type = lookup_event(name, warnings, C_ARRAY_SIZE(warnings)); if (sample.event.type == SAMPLE_EVENT_NONE) return; @@ -743,6 +802,15 @@ static void sample_event_alarm_type(const struct type_desc *desc, struct sample_ static void sample_event_alarm_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) { + static const eon_event_t alarms[] = { + {"Mandatory Safety Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP}, + {"Ascent Speed", SAMPLE_EVENT_ASCENT}, + {"Diluent Hyperoxia", SAMPLE_EVENT_NONE}, + {"Violated Deep Stop", SAMPLE_EVENT_VIOLATION}, + {"Ceiling Broken", SAMPLE_EVENT_CEILING}, + {"PO2 High", SAMPLE_EVENT_PO2}, + {"PO2 Low", SAMPLE_EVENT_PO2}, + }; dc_sample_value_t sample = {0}; const char *name; @@ -750,7 +818,7 @@ static void sample_event_alarm_value(const struct type_desc *desc, struct sample if (!name) return; - sample.event.type = SAMPLE_EVENT_NONE; + sample.event.type = lookup_event(name, alarms, C_ARRAY_SIZE(alarms)); if (sample.event.type == SAMPLE_EVENT_NONE) return; From f30e048afcb5160b984b76ff46858cb680d6fd5b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 11 Aug 2016 16:03:57 +0200 Subject: [PATCH 14/19] Add a configure option to build without libusb. --- configure.ac | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index b5aa34b..b8c25dc 100644 --- a/configure.ac +++ b/configure.ac @@ -74,11 +74,17 @@ AC_MSG_RESULT([$os_win32]) AM_CONDITIONAL([OS_WIN32], [test "$os_win32" = "yes"]) # Checks for USB support. -PKG_CHECK_MODULES([LIBUSB], [libusb-1.0], [have_libusb=yes], [have_libusb=no]) -if test "$have_libusb" = "yes"; then - AC_DEFINE([HAVE_LIBUSB], [1], [libusb support]) - AC_SUBST([DEPENDENCIES], [libusb-1.0]) -fi +AC_ARG_WITH([libusb], + [AS_HELP_STRING([--without-libusb], + [Build without the libusb library])], + [], [with_libusb=auto]) +AS_IF([test "x$with_libusb" != "xno"], [ + PKG_CHECK_MODULES([LIBUSB], [libusb-1.0], [have_libusb=yes], [have_libusb=no]) + AS_IF([test "x$have_libusb" = "xyes"], [ + AC_DEFINE([HAVE_LIBUSB], [1], [libusb library]) + AC_SUBST([DEPENDENCIES], [libusb-1.0]) + ]) +]) # Checks for IrDA support. AC_CHECK_HEADERS([winsock2.h af_irda.h], [irda_win32=yes], [irda_win32=no], [ From bae6cb856ea36d965813a9cc7d23336a6f92d087 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 18 Jul 2016 08:47:57 +0200 Subject: [PATCH 15/19] Add a new USB HID communication backend. --- src/Makefile.am | 2 + src/usbhid.c | 347 ++++++++++++++++++++++++++++++++++++++++++++++++ src/usbhid.h | 122 +++++++++++++++++ 3 files changed, 471 insertions(+) create mode 100644 src/usbhid.c create mode 100644 src/usbhid.h diff --git a/src/Makefile.am b/src/Makefile.am index ed7b6f8..e52d1cd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -83,6 +83,8 @@ else libdivecomputer_la_SOURCES += irda.h irda_dummy.c endif +libdivecomputer_la_SOURCES += usbhid.h usbhid.c + if OS_WIN32 libdivecomputer_la_SOURCES += libdivecomputer.rc endif diff --git a/src/usbhid.c b/src/usbhid.c new file mode 100644 index 0000000..1bdb1fd --- /dev/null +++ b/src/usbhid.c @@ -0,0 +1,347 @@ +/* + * libdivecomputer + * + * Copyright (C) 2016 Jef Driesen + * + * 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#if defined(HAVE_LIBUSB) +#ifdef _WIN32 +#define NOGDI +#endif +#include +#endif + +#include "usbhid.h" +#include "common-private.h" +#include "context-private.h" + +struct dc_usbhid_t { + /* Library context. */ + dc_context_t *context; + /* Internal state. */ +#if defined(HAVE_LIBUSB) + libusb_context *ctx; + libusb_device_handle *handle; + int interface; + unsigned char endpoint_in; + unsigned char endpoint_out; + unsigned int timeout; +#endif +}; + +#if defined(HAVE_LIBUSB) +static dc_status_t +syserror(int errcode) +{ + switch (errcode) { + case LIBUSB_ERROR_INVALID_PARAM: + return DC_STATUS_INVALIDARGS; + case LIBUSB_ERROR_NO_MEM: + return DC_STATUS_NOMEMORY; + case LIBUSB_ERROR_NO_DEVICE: + case LIBUSB_ERROR_NOT_FOUND: + return DC_STATUS_NODEVICE; + case LIBUSB_ERROR_ACCESS: + case LIBUSB_ERROR_BUSY: + return DC_STATUS_NOACCESS; + case LIBUSB_ERROR_TIMEOUT: + return DC_STATUS_TIMEOUT; + default: + return DC_STATUS_IO; + } +} +#endif + +dc_status_t +dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsigned int pid) +{ + dc_status_t status = DC_STATUS_SUCCESS; + dc_usbhid_t *usbhid = NULL; + int rc = 0; + + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + INFO (context, "Open: vid=%04x, pid=%04x", vid, pid); + + // Allocate memory. + usbhid = (dc_usbhid_t *) malloc (sizeof (dc_usbhid_t)); + if (usbhid == NULL) { + ERROR (context, "Out of memory."); + return DC_STATUS_NOMEMORY; + } + + // Library context. + usbhid->context = context; + +#if defined(HAVE_LIBUSB) + struct libusb_device **devices = NULL; + struct libusb_config_descriptor *config = NULL; + + // Initialize the libusb library. + rc = libusb_init (&usbhid->ctx); + if (rc != LIBUSB_SUCCESS) { + ERROR (context, "Failed to initialize usb support (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_free; + } + + // Enumerate the USB devices. + ssize_t ndevices = libusb_get_device_list (usbhid->ctx, &devices); + if (ndevices < 0) { + ERROR (context, "Failed to enumerate the usb devices (%s).", + libusb_error_name (ndevices)); + status = syserror (ndevices); + goto error_usb_exit; + } + + // Find the first device matching the VID/PID. + struct libusb_device *device = NULL; + for (size_t i = 0; i < ndevices; i++) { + struct libusb_device_descriptor desc; + rc = libusb_get_device_descriptor (devices[i], &desc); + if (rc < 0) { + ERROR (context, "Failed to get the device descriptor (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_usb_free_list; + } + + if (desc.idVendor == vid && desc.idProduct == pid) { + device = devices[i]; + break; + } + } + + if (device == NULL) { + ERROR (context, "No matching USB device found."); + status = DC_STATUS_NODEVICE; + goto error_usb_free_list; + } + + // Get the active configuration descriptor. + rc = libusb_get_active_config_descriptor (device, &config); + if (rc != LIBUSB_SUCCESS) { + ERROR (context, "Failed to get the configuration descriptor (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_usb_free_list; + } + + // Find the first HID interface. + const struct libusb_interface_descriptor *interface = NULL; + for (unsigned int i = 0; i < config->bNumInterfaces; i++) { + const struct libusb_interface *iface = &config->interface[i]; + for (unsigned int j = 0; j < iface->num_altsetting; j++) { + const struct libusb_interface_descriptor *desc = &iface->altsetting[j]; + if (desc->bInterfaceClass == LIBUSB_CLASS_HID && interface == NULL) { + interface = desc; + } + } + } + + if (interface == NULL) { + ERROR (context, "No HID interface found."); + status = DC_STATUS_IO; + goto error_usb_free_config; + } + + // Find the first input and output interrupt endpoints. + const struct libusb_endpoint_descriptor *ep_in = NULL, *ep_out = NULL; + for (unsigned int i = 0; i < interface->bNumEndpoints; i++) { + const struct libusb_endpoint_descriptor *desc = &interface->endpoint[i]; + + unsigned int type = desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK; + unsigned int direction = desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK; + + if (type != LIBUSB_TRANSFER_TYPE_INTERRUPT) + continue; + + if (direction == LIBUSB_ENDPOINT_IN && ep_in == NULL) { + ep_in = desc; + } + + if (direction == LIBUSB_ENDPOINT_OUT && ep_out == NULL) { + ep_out = desc; + } + } + + if (ep_in == NULL || ep_out == NULL) { + ERROR (context, "No interrupt endpoints found."); + status = DC_STATUS_IO; + goto error_usb_free_config; + } + + usbhid->interface = interface->bInterfaceNumber; + usbhid->endpoint_in = ep_in->bEndpointAddress; + usbhid->endpoint_out = ep_out->bEndpointAddress; + usbhid->timeout = 0; + + INFO (context, "Open: interface=%u, endpoints=%02x,%02x", + usbhid->interface, usbhid->endpoint_in, usbhid->endpoint_out); + + // Open the USB device. + rc = libusb_open (device, &usbhid->handle); + if (rc != LIBUSB_SUCCESS) { + ERROR (context, "Failed to open the usb device (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_usb_free_config; + } + +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102) + libusb_set_auto_detach_kernel_driver (usbhid->handle, 1); +#endif + + // Claim the HID interface. + rc = libusb_claim_interface (usbhid->handle, usbhid->interface); + if (rc != LIBUSB_SUCCESS) { + ERROR (context, "Failed to claim the usb interface (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto error_usb_close; + } + + libusb_free_config_descriptor (config); + libusb_free_device_list (devices, 1); +#endif + + *out = usbhid; + + return DC_STATUS_SUCCESS; + +#if defined(HAVE_LIBUSB) +error_usb_close: + libusb_close (usbhid->handle); +error_usb_free_config: + libusb_free_config_descriptor (config); +error_usb_free_list: + libusb_free_device_list (devices, 1); +error_usb_exit: + libusb_exit (usbhid->ctx); +#endif +error_free: + free (usbhid); + return status; +} + +dc_status_t +dc_usbhid_close (dc_usbhid_t *usbhid) +{ + dc_status_t status = DC_STATUS_SUCCESS; + + if (usbhid == NULL) + return DC_STATUS_SUCCESS; + +#if defined(HAVE_LIBUSB) + libusb_release_interface (usbhid->handle, usbhid->interface); + libusb_close (usbhid->handle); + libusb_exit (usbhid->ctx); +#endif + free (usbhid); + + return status; +} + +dc_status_t +dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) +{ + if (usbhid == NULL) + return DC_STATUS_INVALIDARGS; + + INFO (usbhid->context, "Timeout: value=%i", timeout); + +#if defined(HAVE_LIBUSB) + if (timeout < 0) { + usbhid->timeout = 0; + } else if (timeout == 0) { + return DC_STATUS_UNSUPPORTED; + } else { + usbhid->timeout = timeout; + } +#endif + + return DC_STATUS_SUCCESS; +} + +dc_status_t +dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + int nbytes = 0; + + if (usbhid == NULL) { + status = DC_STATUS_INVALIDARGS; + goto out; + } + +#if defined(HAVE_LIBUSB) + int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_in, data, size, &nbytes, usbhid->timeout); + if (rc != LIBUSB_SUCCESS) { + ERROR (usbhid->context, "Usb read interrupt transfer failed (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto out; + } +#endif + +out: + HEXDUMP (usbhid->context, DC_LOGLEVEL_INFO, "Read", (unsigned char *) data, nbytes); + + if (actual) + *actual = nbytes; + + return status; +} + +dc_status_t +dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *actual) +{ + dc_status_t status = DC_STATUS_SUCCESS; + int nbytes = 0; + + if (usbhid == NULL) { + status = DC_STATUS_INVALIDARGS; + goto out; + } + +#if defined(HAVE_LIBUSB) + int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_out, (void *) data, size, &nbytes, 0); + if (rc != LIBUSB_SUCCESS) { + ERROR (usbhid->context, "Usb write interrupt transfer failed (%s).", + libusb_error_name (rc)); + status = syserror (rc); + goto out; + } +#endif + +out: + HEXDUMP (usbhid->context, DC_LOGLEVEL_INFO, "Write", (unsigned char *) data, nbytes); + + if (actual) + *actual = nbytes; + + return status; +} diff --git a/src/usbhid.h b/src/usbhid.h new file mode 100644 index 0000000..65d98c1 --- /dev/null +++ b/src/usbhid.h @@ -0,0 +1,122 @@ +/* + * libdivecomputer + * + * Copyright (C) 2016 Jef Driesen + * + * 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 DC_USBHID_H +#define DC_USBHID_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Opaque object representing a USB HID connection. + */ +typedef struct dc_usbhid_t dc_usbhid_t; + +/** + * Open a USB HID connection. + * + * @param[out] usbhid A location to store the USB HID connection. + * @param[in] context A valid context object. + * @param[in] vid The USB Vendor ID of the device. + * @param[in] pid The USB Product ID of the device. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_usbhid_open (dc_usbhid_t **usbhid, dc_context_t *context, unsigned int vid, unsigned int pid); + +/** + * Close the connection and free all resources. + * + * @param[in] usbhid A valid USB HID connection. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_usbhid_close (dc_usbhid_t *usbhid); + +/** + * Set the read timeout. + * + * There are three distinct modes available: + * + * 1. Blocking (timeout < 0): + * + * The read operation is blocked until all the requested bytes have + * been received. If the requested number of bytes does not arrive, + * the operation will block forever. + * + * 2. Non-blocking (timeout == 0): + * + * The read operation returns immediately with the bytes that have + * already been received, even if no bytes have been received. + * + * 3. Timeout (timeout > 0): + * + * The read operation is blocked until all the requested bytes have + * been received. If the requested number of bytes does not arrive + * within the specified amount of time, the operation will return + * with the bytes that have already been received. + * + * @param[in] usbhid A valid USB HID connection. + * @param[in] timeout The timeout in milliseconds. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout); + +/** + * Read data from the USB HID connection. + * + * @param[in] usbhid A valid USB HID connection. + * @param[out] data The memory buffer to read the data into. + * @param[in] size The number of bytes to read. + * @param[out] actual An (optional) location to store the actual + * number of bytes transferred. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual); + +/** + * Write data to the USB HID connection. + * + * @param[in] usbhid A valid USB HID connection. + * @param[in] data The memory buffer to write the data from. + * @param[in] size The number of bytes to write. + * @param[out] actual An (optional) location to store the actual + * number of bytes transferred. + * @returns #DC_STATUS_SUCCESS on success, or another #dc_status_t code + * on failure. + */ +dc_status_t +dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *actual); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DC_USBHID_H */ From ed2a7c91feb7bb207db124f5b4a05917950f00c0 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 18 Aug 2016 08:25:02 +0200 Subject: [PATCH 16/19] Use the hidapi library on Mac OS X. On Mac OS X, libusb doesn't work for USB HID devices. We can use the hidapi library instead. Although the hidapi library supports Linux and Windows too, we keep using libusb there to avoid the extra dependency. --- configure.ac | 19 +++++++++++++- src/Makefile.am | 4 +-- src/usbhid.c | 69 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index b8c25dc..0bb834f 100644 --- a/configure.ac +++ b/configure.ac @@ -73,6 +73,8 @@ esac AC_MSG_RESULT([$os_win32]) AM_CONDITIONAL([OS_WIN32], [test "$os_win32" = "yes"]) +DEPENDENCIES="" + # Checks for USB support. AC_ARG_WITH([libusb], [AS_HELP_STRING([--without-libusb], @@ -82,10 +84,25 @@ AS_IF([test "x$with_libusb" != "xno"], [ PKG_CHECK_MODULES([LIBUSB], [libusb-1.0], [have_libusb=yes], [have_libusb=no]) AS_IF([test "x$have_libusb" = "xyes"], [ AC_DEFINE([HAVE_LIBUSB], [1], [libusb library]) - AC_SUBST([DEPENDENCIES], [libusb-1.0]) + DEPENDENCIES="$DEPENDENCIES libusb-1.0" ]) ]) +# Checks for HIDAPI support. +AC_ARG_WITH([hidapi], + [AS_HELP_STRING([--without-hidapi], + [Build without the hidapi library])], + [], [with_hidapi=auto]) +AS_IF([test "x$with_hidapi" != "xno"], [ + PKG_CHECK_MODULES([HIDAPI], [hidapi], [have_hidapi=yes], [have_hidapi=no]) + AS_IF([test "x$have_hidapi" = "xyes"], [ + AC_DEFINE([HAVE_HIDAPI], [1], [hidapi library]) + DEPENDENCIES="$DEPENDENCIES hidapi" + ]) +]) + +AC_SUBST([DEPENDENCIES]) + # Checks for IrDA support. AC_CHECK_HEADERS([winsock2.h af_irda.h], [irda_win32=yes], [irda_win32=no], [ #if HAVE_WINSOCK2_H diff --git a/src/Makefile.am b/src/Makefile.am index e52d1cd..fba41a8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,9 +1,9 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include -AM_CFLAGS = $(LIBUSB_CFLAGS) -DENABLE_DEPRECATED +AM_CFLAGS = $(LIBUSB_CFLAGS) $(HIDAPI_CFLAGS) -DENABLE_DEPRECATED lib_LTLIBRARIES = libdivecomputer.la -libdivecomputer_la_LIBADD = $(LIBUSB_LIBS) -lm +libdivecomputer_la_LIBADD = $(LIBUSB_LIBS) $(HIDAPI_LIBS) -lm libdivecomputer_la_LDFLAGS = \ -version-info $(DC_VERSION_LIBTOOL) \ -no-undefined \ diff --git a/src/usbhid.c b/src/usbhid.c index 1bdb1fd..f5b602d 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -25,11 +25,13 @@ #include -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) #ifdef _WIN32 #define NOGDI #endif #include +#elif defined(HAVE_HIDAPI) +#include #endif #include "usbhid.h" @@ -40,17 +42,20 @@ struct dc_usbhid_t { /* Library context. */ dc_context_t *context; /* Internal state. */ -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) libusb_context *ctx; libusb_device_handle *handle; int interface; unsigned char endpoint_in; unsigned char endpoint_out; unsigned int timeout; +#elif defined(HAVE_HIDAPI) + hid_device *handle; + int timeout; #endif }; -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) static dc_status_t syserror(int errcode) { @@ -95,7 +100,7 @@ dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsi // Library context. usbhid->context = context; -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) struct libusb_device **devices = NULL; struct libusb_config_descriptor *config = NULL; @@ -226,13 +231,33 @@ dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsi libusb_free_config_descriptor (config); libusb_free_device_list (devices, 1); + +#elif defined(HAVE_HIDAPI) + + // Initialize the hidapi library. + rc = hid_init(); + if (rc < 0) { + ERROR (context, "Failed to initialize usb support."); + status = DC_STATUS_IO; + goto error_free; + } + + // Open the USB device. + usbhid->handle = hid_open (vid, pid, NULL); + if (usbhid->handle == NULL) { + ERROR (context, "Failed to open the usb device."); + status = DC_STATUS_IO; + goto error_hid_exit; + } + + usbhid->timeout = -1; #endif *out = usbhid; return DC_STATUS_SUCCESS; -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) error_usb_close: libusb_close (usbhid->handle); error_usb_free_config: @@ -241,6 +266,9 @@ error_usb_free_list: libusb_free_device_list (devices, 1); error_usb_exit: libusb_exit (usbhid->ctx); +#elif defined(HAVE_HIDAPI) +error_hid_exit: + hid_exit (); #endif error_free: free (usbhid); @@ -255,10 +283,13 @@ dc_usbhid_close (dc_usbhid_t *usbhid) if (usbhid == NULL) return DC_STATUS_SUCCESS; -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) libusb_release_interface (usbhid->handle, usbhid->interface); libusb_close (usbhid->handle); libusb_exit (usbhid->ctx); +#elif defined(HAVE_HIDAPI) + hid_close(usbhid->handle); + hid_exit(); #endif free (usbhid); @@ -273,7 +304,7 @@ dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) INFO (usbhid->context, "Timeout: value=%i", timeout); -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) if (timeout < 0) { usbhid->timeout = 0; } else if (timeout == 0) { @@ -281,6 +312,12 @@ dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) } else { usbhid->timeout = timeout; } +#elif defined(HAVE_HIDAPI) + if (timeout < 0) { + usbhid->timeout = -1; + } else { + usbhid->timeout = timeout; + } #endif return DC_STATUS_SUCCESS; @@ -297,7 +334,7 @@ dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual) goto out; } -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_in, data, size, &nbytes, usbhid->timeout); if (rc != LIBUSB_SUCCESS) { ERROR (usbhid->context, "Usb read interrupt transfer failed (%s).", @@ -305,6 +342,13 @@ dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual) status = syserror (rc); goto out; } +#elif defined(HAVE_HIDAPI) + nbytes = hid_read_timeout(usbhid->handle, data, size, usbhid->timeout); + if (nbytes < 0) { + ERROR (usbhid->context, "Usb read interrupt transfer failed."); + status = DC_STATUS_IO; + goto out; + } #endif out: @@ -327,7 +371,7 @@ dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *act goto out; } -#if defined(HAVE_LIBUSB) +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) int rc = libusb_interrupt_transfer (usbhid->handle, usbhid->endpoint_out, (void *) data, size, &nbytes, 0); if (rc != LIBUSB_SUCCESS) { ERROR (usbhid->context, "Usb write interrupt transfer failed (%s).", @@ -335,6 +379,13 @@ dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *act status = syserror (rc); goto out; } +#elif defined(HAVE_HIDAPI) + nbytes = hid_write(usbhid->handle, data, size); + if (nbytes < 0) { + ERROR (usbhid->context, "Usb write interrupt transfer failed."); + status = DC_STATUS_IO; + goto out; + } #endif out: From 6c6b144fe01a248c0bb848d0daa456ef06c23ba5 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 30 Aug 2016 11:19:54 +0200 Subject: [PATCH 17/19] Add a dummy backend for systems without USB HID support. This dummy implemantion is used when building without libusb and hidapi. --- src/usbhid.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/usbhid.c b/src/usbhid.c index f5b602d..44c0886 100644 --- a/src/usbhid.c +++ b/src/usbhid.c @@ -26,11 +26,13 @@ #include #if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#define USBHID #ifdef _WIN32 #define NOGDI #endif #include #elif defined(HAVE_HIDAPI) +#define USBHID #include #endif @@ -81,6 +83,7 @@ syserror(int errcode) dc_status_t dc_usbhid_open (dc_usbhid_t **out, dc_context_t *context, unsigned int vid, unsigned int pid) { +#ifdef USBHID dc_status_t status = DC_STATUS_SUCCESS; dc_usbhid_t *usbhid = NULL; int rc = 0; @@ -273,11 +276,15 @@ error_hid_exit: error_free: free (usbhid); return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_usbhid_close (dc_usbhid_t *usbhid) { +#ifdef USBHID dc_status_t status = DC_STATUS_SUCCESS; if (usbhid == NULL) @@ -294,11 +301,15 @@ dc_usbhid_close (dc_usbhid_t *usbhid) free (usbhid); return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) { +#ifdef USBHID if (usbhid == NULL) return DC_STATUS_INVALIDARGS; @@ -321,11 +332,15 @@ dc_usbhid_set_timeout (dc_usbhid_t *usbhid, int timeout) #endif return DC_STATUS_SUCCESS; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_usbhid_read (dc_usbhid_t *usbhid, void *data, size_t size, size_t *actual) { +#ifdef USBHID dc_status_t status = DC_STATUS_SUCCESS; int nbytes = 0; @@ -358,11 +373,15 @@ out: *actual = nbytes; return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } dc_status_t dc_usbhid_write (dc_usbhid_t *usbhid, const void *data, size_t size, size_t *actual) { +#ifdef USBHID dc_status_t status = DC_STATUS_SUCCESS; int nbytes = 0; @@ -395,4 +414,7 @@ out: *actual = nbytes; return status; +#else + return DC_STATUS_UNSUPPORTED; +#endif } From 417ee6e6192ee3dbcc0c2d2bd899e41ca8e5d071 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 18 Jul 2016 08:50:46 +0200 Subject: [PATCH 18/19] Use the new USB HID backend for the Eon Steel. --- src/descriptor.c | 8 +++- src/suunto_eonsteel.c | 89 +++++++++++++------------------------------ 2 files changed, 33 insertions(+), 64 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 84f6028..3efb036 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -23,6 +23,12 @@ #include "config.h" #endif +#if defined(HAVE_LIBUSB) && !defined(__APPLE__) +#define USBHID +#elif defined(HAVE_HIDAPI) +#define USBHID +#endif + #include #include @@ -80,7 +86,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Suunto", "Vyper Novo", DC_FAMILY_SUUNTO_D9, 0x1D}, {"Suunto", "Zoop Novo", DC_FAMILY_SUUNTO_D9, 0x1E}, /* Suunto EON Steel */ -#ifdef HAVE_LIBUSB +#ifdef USBHID {"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EONSTEEL, 0}, #endif /* Uwatec Aladin */ diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c index a84eaea..4f48bc8 100644 --- a/src/suunto_eonsteel.c +++ b/src/suunto_eonsteel.c @@ -19,10 +19,6 @@ * MA 02110-1301 USA */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - #include #include #include @@ -32,24 +28,15 @@ #include "context-private.h" #include "device-private.h" #include "array.h" +#include "usbhid.h" #ifdef _MSC_VER #define snprintf _snprintf #endif -#ifdef HAVE_LIBUSB - -#ifdef _WIN32 -#define NOGDI -#endif - -#include - typedef struct suunto_eonsteel_device_t { dc_device_t base; - - libusb_context *ctx; - libusb_device_handle *handle; + dc_usbhid_t *usbhid; unsigned int magic; unsigned short seq; unsigned char version[0x30]; @@ -143,13 +130,13 @@ static void put_le32(unsigned int val, unsigned char *p) static int receive_packet(suunto_eonsteel_device_t *eon, unsigned char *buffer, int size) { unsigned char buf[64]; - const int InEndpoint = 0x82; - int rc, transferred, len; + dc_status_t rc = DC_STATUS_SUCCESS; + size_t transferred = 0; + int len; - /* 5000 = 5s timeout */ - rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, PACKET_SIZE, &transferred, 5000); - if (rc) { - ERROR(eon->base.context, "read interrupt transfer failed (%s)", libusb_error_name(rc)); + rc = dc_usbhid_read(eon->usbhid, buf, PACKET_SIZE, &transferred); + if (rc != DC_STATUS_SUCCESS) { + ERROR(eon->base.context, "read interrupt transfer failed"); return -1; } if (transferred != PACKET_SIZE) { @@ -179,11 +166,11 @@ static int send_cmd(suunto_eonsteel_device_t *eon, unsigned int len, const unsigned char *buffer) { - const int OutEndpoint = 0x02; unsigned char buf[64]; - int transferred, rc; unsigned short seq = eon->seq; unsigned int magic = eon->magic; + dc_status_t rc = DC_STATUS_SUCCESS; + size_t transferred = 0; // Two-byte packet header, followed by 12 bytes of extended header if (len > sizeof(buf)-2-12) { @@ -213,8 +200,8 @@ static int send_cmd(suunto_eonsteel_device_t *eon, memcpy(buf+14, buffer, len); } - rc = libusb_interrupt_transfer(eon->handle, OutEndpoint, buf, sizeof(buf), &transferred, 5000); - if (rc < 0) { + rc = dc_usbhid_write(eon->usbhid, buf, sizeof(buf), &transferred); + if (rc != DC_STATUS_SUCCESS) { ERROR(eon->base.context, "write interrupt transfer failed"); return -1; } @@ -525,22 +512,25 @@ static int get_file_list(suunto_eonsteel_device_t *eon, struct directory_entry * static int initialize_eonsteel(suunto_eonsteel_device_t *eon) { - const int InEndpoint = 0x82; const unsigned char init[] = {0x02, 0x00, 0x2a, 0x00}; unsigned char buf[64]; struct eon_hdr hdr; + dc_usbhid_set_timeout(eon->usbhid, 10); + /* Get rid of any pending stale input first */ for (;;) { - int transferred; + size_t transferred = 0; - int rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, sizeof(buf), &transferred, 10); - if (rc < 0) + dc_status_t rc = dc_usbhid_read(eon->usbhid, buf, sizeof(buf), &transferred); + if (rc != DC_STATUS_SUCCESS) break; if (!transferred) break; } + dc_usbhid_set_timeout(eon->usbhid, 5000); + if (send_cmd(eon, INIT_CMD, sizeof(init), init)) { ERROR(eon->base.context, "Failed to send initialization command"); return -1; @@ -576,39 +566,24 @@ suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, const char memset (eon->version, 0, sizeof (eon->version)); memset (eon->fingerprint, 0, sizeof (eon->fingerprint)); - if (libusb_init(&eon->ctx)) { - ERROR(context, "libusb_init() failed"); - status = DC_STATUS_IO; + status = dc_usbhid_open(&eon->usbhid, context, 0x1493, 0x0030); + if (status != DC_STATUS_SUCCESS) { + ERROR(context, "unable to open device"); goto error_free; } - eon->handle = libusb_open_device_with_vid_pid(eon->ctx, 0x1493, 0x0030); - if (!eon->handle) { - ERROR(context, "unable to open device"); - status = DC_STATUS_IO; - goto error_usb_exit; - } - -#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102) - libusb_set_auto_detach_kernel_driver(eon->handle, 1); -#endif - - libusb_claim_interface(eon->handle, 0); - if (initialize_eonsteel(eon) < 0) { ERROR(context, "unable to initialize device"); status = DC_STATUS_IO; - goto error_usb_close; + goto error_close; } *out = (dc_device_t *) eon; return DC_STATUS_SUCCESS; -error_usb_close: - libusb_close(eon->handle); -error_usb_exit: - libusb_exit(eon->ctx); +error_close: + dc_usbhid_close(eon->usbhid); error_free: free(eon); return status; @@ -732,19 +707,7 @@ suunto_eonsteel_device_close(dc_device_t *abstract) { suunto_eonsteel_device_t *eon = (suunto_eonsteel_device_t *) abstract; - libusb_close(eon->handle); - libusb_exit(eon->ctx); + dc_usbhid_close(eon->usbhid); return DC_STATUS_SUCCESS; } - -#else // no LIBUSB support - -dc_status_t -suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, const char *name, unsigned int model) -{ - ERROR(context, "The Suunto EON Steel backend needs libusb-1.0"); - return DC_STATUS_UNSUPPORTED; -} - -#endif From 03c252335b8312299f874f4e8eba68dd2e039f42 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 18 Sep 2016 23:40:16 +0200 Subject: [PATCH 19/19] Fix the firmware version for the HW Frog. The firmware version in the dive header is stored at byte offset 32 instead of 34. --- src/hw_ostc_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hw_ostc_parser.c b/src/hw_ostc_parser.c index 7401bc3..48eae3d 100644 --- a/src/hw_ostc_parser.c +++ b/src/hw_ostc_parser.c @@ -135,7 +135,7 @@ static const hw_ostc_layout_t hw_ostc_layout_frog = { 43, /* salinity */ 47, /* duration */ 19, /* temperature */ - 34, /* firmware */ + 32, /* firmware */ }; static const hw_ostc_layout_t hw_ostc_layout_ostc3 = {