From 8e3cb0542f478ee94f49ac6abe25c50a0c63efac Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 20 May 2015 14:16:56 -0700 Subject: [PATCH 1/9] suunto eon steel: fix file reading special case The "receive_data()" function would continue to try to read packets as long as the previous packet was full-sized, but with just the right size of file and the right chunking, the file might end at a packet boundary. Then receive_data() would try to read more data, which fails - there are no more packets, despite the last packet being full. This never triggered for me, but Robert Helling forwarded a data dump of a filure to read a dive due to this. Since I don't trigger this case, I can't really test it, but I did check that the new "stop early" logic works for me (ie never triggers ;). Reported-by: Robert C. Helling Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c index ab090bf..3524903 100644 --- a/src/suunto_eonsteel.c +++ b/src/suunto_eonsteel.c @@ -131,7 +131,7 @@ static int receive_data(suunto_eonsteel_device_t *eon, unsigned char *buffer, in unsigned char buf[64]; int ret = 0; - for (;;) { + while (size > 0) { int rc, transferred, len; rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, sizeof(buf), &transferred, 5000); @@ -233,12 +233,15 @@ static int send_receive(suunto_eonsteel_device_t *eon, unsigned int len_out, const unsigned char *out, unsigned int len_in, unsigned char *in) { - int len, actual; + int len, actual, max; unsigned char buf[2048]; if (send_cmd(eon, cmd, len_out, out) < 0) return -1; - len = receive_data(eon, buf, sizeof(buf)); + max = len_in + 12; + if (max > sizeof(buf)) + max = sizeof(buf); + len = receive_data(eon, buf, max); if (len < 10) { ERROR(eon->base.context, "short command reply (%d)", len); return -1; @@ -316,7 +319,7 @@ static int read_file(suunto_eonsteel_device_t *eon, const char *filename, dc_buf put_le32(ask, cmdbuf+4); // Size of read rc = send_receive(eon, FILE_READ_CMD, 8, cmdbuf, - sizeof(result), result); + ask+8, result); if (rc < 0) { ERROR(eon->base.context, "unable to read %s", filename); return -1; From e73dcdacaeb5fc4db3bf8b837ff9476e2dcf18cc Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 21 May 2015 11:02:11 -0700 Subject: [PATCH 2/9] suunto eon steel: be more explicit about transfer sizes When reading data from the EON Steel, we'd generally continue reading until we saw that a response was done by seeing a packet that wasn't full. That broke for the case of the data boundary matching the packet boundary, fixed by the commit "suunto eon steel: fix file reading special case". However, that commit only fixed it for the case of reading a file, where the result has a size that is known up-front. And most other situations really don't matter, because the result size is fixed and fits in a single packet, so it all works. However, there are still a few cases that could trigger the problem, notably reading the directory contents. So change the send_receive() logic to actually read the expected size from the receive header in the first packet of the reply. This means that we need to re-organize the packet reception code a bit, but the end result is that we are much more careful about data sizes, This also changes the packet logging to be much more readable, by logging just the actual data, and not the (uninteresting) per-packet header, or the stale data at the end of the packet. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel.c | 174 +++++++++++++++++++++++++++++------------- 1 file changed, 120 insertions(+), 54 deletions(-) diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c index 3524903..5713aa4 100644 --- a/src/suunto_eonsteel.c +++ b/src/suunto_eonsteel.c @@ -125,43 +125,45 @@ static void put_le32(unsigned int val, unsigned char *p) p[3] = val >> 24; } -static int receive_data(suunto_eonsteel_device_t *eon, unsigned char *buffer, int size) +/* + * Get a single 64-byte packet from the dive computer. This handles packet + * logging and any obvious packet-level errors, and returns the payload of + * packet. + * + * The two first bytes of the packet are packet-level metadata: the report + * type (always 0x3f), and then the size of the valid data in the packet. + * + * The maximum payload is 62 bytes. + */ +#define PACKET_SIZE 64 +static int receive_packet(suunto_eonsteel_device_t *eon, unsigned char *buffer, int size) { - const int InEndpoint = 0x82; unsigned char buf[64]; - int ret = 0; + const int InEndpoint = 0x82; + int rc, transferred, len; - while (size > 0) { - int rc, transferred, len; - - rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, sizeof(buf), &transferred, 5000); - if (rc || transferred != sizeof(buf)) { - ERROR(eon->base.context, "incomplete read interrupt transfer"); - return -1; - } - // dump every incoming packet? - HEXDUMP (eon->base.context, DC_LOGLEVEL_DEBUG, "rcv", buf, transferred); - if (buf[0] != 0x3f) { - ERROR(eon->base.context, "read interrupt transfer returns wrong report type"); - return -1; - } - len = buf[1]; - if (len > sizeof(buf)-2) { - ERROR(eon->base.context, "read interrupt transfer reports short length"); - return -1; - } - if (len > size) { - ERROR(eon->base.context, "read interrupt transfer reports excessive length"); - return -1; - } - memcpy(buffer+ret, buf+2, len); - size -= len; - ret += len; - if (len < sizeof(buf)-2) - break; + /* 5000 = 5s timeout */ + rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, PACKET_SIZE, &transferred, 5000); + if (rc || transferred != PACKET_SIZE) { + ERROR(eon->base.context, "incomplete read interrupt transfer"); + return -1; } - - return ret; + if (buf[0] != 0x3f) { + ERROR(eon->base.context, "read interrupt transfer returns wrong report type (%d)", buf[0]); + return -1; + } + len = buf[1]; + if (len > PACKET_SIZE-2) { + ERROR(eon->base.context, "read interrupt transfer reports bad length (%d)", len); + return -1; + } + if (len > size) { + ERROR(eon->base.context, "receive_packet result buffer too small - truncating"); + len = size; + } + HEXDUMP (eon->base.context, DC_LOGLEVEL_DEBUG, "rcv", buf+2, len); + memcpy(buffer, buf+2, len); + return len; } static int send_cmd(suunto_eonsteel_device_t *eon, @@ -210,10 +212,67 @@ static int send_cmd(suunto_eonsteel_device_t *eon, } // dump every outgoing packet? - HEXDUMP (eon->base.context, DC_LOGLEVEL_DEBUG, "cmd", buf, sizeof(buf)); + HEXDUMP (eon->base.context, DC_LOGLEVEL_DEBUG, "cmd", buf+2, len+12); return 0; } +struct eon_hdr { + unsigned short cmd; + unsigned int magic; + unsigned short seq; + unsigned int len; +}; + +static int receive_header(suunto_eonsteel_device_t *eon, struct eon_hdr *hdr, unsigned char *buffer, int size) +{ + int ret; + unsigned char header[64]; + + ret = receive_packet(eon, header, sizeof(header)); + if (ret < 0) + return -1; + if (ret < 12) { + ERROR(eon->base.context, "short reply packet (%d)", ret); + return -1; + } + + /* Unpack the 12-byte header */ + hdr->cmd = array_uint16_le(header); + hdr->magic = array_uint32_le(header+2); + hdr->seq = array_uint16_le(header+6); + hdr->len = array_uint32_le(header+8); + + ret -= 12; + if (ret > size) { + ERROR(eon->base.context, "receive_header result data buffer too small (%d vs %d)", ret, size); + return -1; + } + memcpy(buffer, header+12, ret); + return ret; +} + +static int receive_data(suunto_eonsteel_device_t *eon, unsigned char *buffer, int size) +{ + int ret = 0; + + while (size > 0) { + int len; + + len = receive_packet(eon, buffer + ret, size); + if (len < 0) + return -1; + + size -= len; + ret += len; + + /* Was it not a full packet of data? We're done, regardless of expectations */ + if (len < PACKET_SIZE-2) + break; + } + + return ret; +} + /* * Send a command, receive a reply * @@ -235,43 +294,49 @@ static int send_receive(suunto_eonsteel_device_t *eon, { int len, actual, max; unsigned char buf[2048]; + struct eon_hdr hdr; if (send_cmd(eon, cmd, len_out, out) < 0) return -1; - max = len_in + 12; - if (max > sizeof(buf)) - max = sizeof(buf); - len = receive_data(eon, buf, max); - if (len < 10) { - ERROR(eon->base.context, "short command reply (%d)", len); + + /* Get the header and the first part of the data */ + len = receive_header(eon, &hdr, in, len_in); + if (len < 0) return -1; - } - if (array_uint16_le(buf) != cmd) { + + /* Verify the header data */ + if (hdr.cmd != cmd) { ERROR(eon->base.context, "command reply doesn't match command"); return -1; } - if (array_uint32_le(buf+2) != eon->magic + 5) { - ERROR(eon->base.context, "command reply doesn't match magic (got %08x, expected %08x)", array_uint32_le(buf+2), eon->magic + 5); + if (hdr.magic != eon->magic + 5) { + ERROR(eon->base.context, "command reply doesn't match magic (got %08x, expected %08x)", hdr.magic, eon->magic + 5); return -1; } - if (array_uint16_le(buf+6) != eon->seq) { + if (hdr.seq != eon->seq) { ERROR(eon->base.context, "command reply doesn't match sequence number"); return -1; } - actual = array_uint32_le(buf+8); - if (actual + 12 != len) { - ERROR(eon->base.context, "command reply length mismatch (got %d, claimed %d)", len-12, actual); + actual = hdr.len; + if (actual < len) { + ERROR(eon->base.context, "command reply length mismatch (got %d, claimed %d)", len, actual); return -1; } - if (len_in < actual) { - ERROR(eon->base.context, "command reply returned too much data (got %d, had %d)", actual, len_in); + if (actual > len_in) { + ERROR(eon->base.context, "command reply too big for result buffer - truncating"); + actual = len_in; + } + + /* Get the rest of the data */ + len += receive_data(eon, in + len, actual - len); + if (len != actual) { + ERROR(eon->base.context, "command reply returned unexpected amoutn of data (got %d, expected %d)", len, actual); return -1; } // Successful command - increment sequence number eon->seq++; - memcpy(in, buf+12, actual); - return actual; + return len; } static int read_file(suunto_eonsteel_device_t *eon, const char *filename, dc_buffer_t *buf) @@ -455,6 +520,7 @@ 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; /* Get rid of any pending stale input first */ for (;;) { @@ -471,13 +537,13 @@ static int initialize_eonsteel(suunto_eonsteel_device_t *eon) ERROR(eon->base.context, "Failed to send initialization command"); return -1; } - if (receive_data(eon, buf, sizeof(buf)) < 0) { + if (receive_header(eon, &hdr, buf, sizeof(buf)) < 0) { ERROR(eon->base.context, "Failed to receive initial reply"); return -1; } // Don't ask - eon->magic = 0x00000005 | (buf[4] << 16) | (buf[5] << 24); + eon->magic = (hdr.magic & 0xffff0000) | 0x0005; // Increment the sequence number for every command sent eon->seq++; return 0; From 9eef4d6cec97af8528d1b1cf8cbe1ede99f4f9ba Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 21 May 2015 14:20:16 -0700 Subject: [PATCH 3/9] suunto eon steel: clean up size limits and error reporting This is some small cleanup after the whole reply size rewrite. It further improves on the error log reporting a bit, and it undoes the "read exact size" thing introduced in "suunto eon steel: fix file reading special case", because it is no longer necessary. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/suunto_eonsteel.c b/src/suunto_eonsteel.c index 5713aa4..feb01a3 100644 --- a/src/suunto_eonsteel.c +++ b/src/suunto_eonsteel.c @@ -144,8 +144,12 @@ static int receive_packet(suunto_eonsteel_device_t *eon, unsigned char *buffer, /* 5000 = 5s timeout */ rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, PACKET_SIZE, &transferred, 5000); - if (rc || transferred != PACKET_SIZE) { - ERROR(eon->base.context, "incomplete read interrupt transfer"); + if (rc) { + ERROR(eon->base.context, "read interrupt transfer failed (%s)", libusb_error_name(rc)); + return -1; + } + if (transferred != PACKET_SIZE) { + ERROR(eon->base.context, "incomplete read interrupt transfer (got %d, expected %d)", transferred, PACKET_SIZE); return -1; } if (buf[0] != 0x3f) { @@ -384,7 +388,7 @@ static int read_file(suunto_eonsteel_device_t *eon, const char *filename, dc_buf put_le32(ask, cmdbuf+4); // Size of read rc = send_receive(eon, FILE_READ_CMD, 8, cmdbuf, - ask+8, result); + sizeof(result), result); if (rc < 0) { ERROR(eon->base.context, "unable to read %s", filename); return -1; From bdae4286392bd87a789f257834ec454f881790cc Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 27 Oct 2014 17:19:48 -0700 Subject: [PATCH 4/9] Suunto EON Steel: populate dive surface pressure Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 104 +++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index b19315b..aa10e12 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -629,6 +629,103 @@ static void add_gas_he(suunto_eonsteel_parser_t *eon, unsigned char he) eon->cache.initialized |= 1 << DC_FIELD_GASMIX; } +static float get_le32_float(const unsigned char *src) +{ + union { + unsigned int val; + float result; + } u; + + u.val = array_uint32_le(src); + return u.result; +} + +// "Device" fields are all utf8: +// Info.BatteryAtEnd +// Info.BatteryAtStart +// Info.BSL +// Info.HW +// Info.SW +// Name +// SerialNumber +static int traverse_device_fields(suunto_eonsteel_parser_t *eon, const char *name, const void *data, int len) +{ + return 0; +} + +// "Header" fields are: +// Activity (utf8) +// DateTime (utf8) +// Depth.Avg (float32,precision=2) +// Depth.Max (float32,precision=2) +// Diving.AlgorithmAscentTime (uint32) +// Diving.AlgorithmBottomMixture.Helium (uint8,precision=2) (0.01*x,100*x) +// Diving.AlgorithmBottomMixture.Oxygen (uint8,precision=2) (0.01*x,100*x) +// Diving.AlgorithmBottomTime (uint32) +// Diving.AlgorithmTransitionDepth (uint8) +// Diving.Algorithm (utf8) +// Diving.Altitude (uint16) +// Diving.Conservatism (int8) +// Diving.DaysInSeries (uint32) +// Diving.DesaturationTime (uint32) +// Diving.DiveMode (utf8) +// Diving.EndTissue.CNS (float32,precision=3) +// Diving.EndTissue.Helium+Pressure (uint32) +// Diving.EndTissue.Nitrogen+Pressure (uint32) +// Diving.EndTissue.OLF (float32,precision=3) +// Diving.EndTissue.OTU (float32) +// Diving.EndTissue.RgbmHelium (float32,precision=3) +// Diving.EndTissue.RgbmNitrogen (float32,precision=3) +// Diving.NumberInSeries (uint32) +// Diving.PreviousDiveDepth (float32,precision=2) +// Diving.StartTissue.CNS (float32,precision=3) +// Diving.StartTissue.Helium+Pressure (uint32) +// Diving.StartTissue.Nitrogen+Pressure (uint32) +// Diving.StartTissue.OLF (float32,precision=3) +// Diving.StartTissue.OTU (float32) +// Diving.StartTissue.RgbmHelium (float32,precision=3) +// Diving.StartTissue.RgbmNitrogen (float32,precision=3) +// Diving.SurfacePressure (uint32) +// Diving.SurfaceTime (uint32) +// Duration (uint32) +// PauseDuration (uint32) +// SampleInterval (uint8) +static int traverse_header_fields(suunto_eonsteel_parser_t *eon, const char *name, const void *data, int len) +{ + if (!strcmp(name, "Depth.Max")) { + double d = get_le32_float(data); + if (d > eon->cache.maxdepth) + eon->cache.maxdepth = d; + return 0; + } + if (!strcmp(name, "Diving.SurfacePressure")) { + unsigned int pressure = array_uint32_le(data); // in SI units - Pascal + eon->cache.surface_pressure = pressure / 100000.0; // bar + eon->cache.initialized |= 1 << DC_FIELD_ATMOSPHERIC; + return 0; + } + + return 0; +} + +static int traverse_dynamic_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, const void *data, int len) +{ + const char *name = desc->desc; + + if (!strncmp(name, "sml.", 4)) { + name += 4; + if (!strncmp(name, "DeviceLog.", 10)) { + name += 10; + if (!strncmp(name, "Device.", 7)) + return traverse_device_fields(eon, name+7, data, len); + if (!strncmp(name, "Header.", 7)) { + return traverse_header_fields(eon, name+7, data, len); + } + } + } + return 0; +} + static int traverse_fields(unsigned short type, const struct type_desc *desc, const unsigned char *data, int len, void *user) { suunto_eonsteel_parser_t *eon = (suunto_eonsteel_parser_t *) user; @@ -653,6 +750,13 @@ static int traverse_fields(unsigned short type, const struct type_desc *desc, co case 0x000f: // Helium percentage in first byte add_gas_he(eon, data[0]); break; + default: + // The types with the high byte set seem to be dynamic + // although not all of them seem to change. But let's + // just check the descriptor name for them. + if (type > 255) + traverse_dynamic_fields(eon, desc, data, len); + break; } return 0; } From a701cc9309ad10cdd3550c86255463838f88577c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 6 Jun 2015 17:14:25 -0700 Subject: [PATCH 5/9] EON Steel: empty descriptor lines are ok Suunto's new v1.1.15 firmware ends up terminating some final descriptor lines with a newline, rather than just using newlines as separators. So the last newline may not be followed by further data, but simple be the end of the string. Accept that case. Signed-off-by: Linus Torvalds --- 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 aa10e12..2998bdb 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -79,6 +79,8 @@ static int record_type(suunto_eonsteel_parser_t *eon, unsigned short type, const next++; } else { len = strlen(name); + if (!len) + break; } if (len < 5 || name[0] != '<' || name[4] != '>') { From af3e099c55e25920c98ad467169ca92bc766d682 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 8 Jun 2015 09:30:57 -0700 Subject: [PATCH 6/9] EON Steel: start moving away from "fixed" fields I was initially fooled into thinking that the field type numbers have some meaning: the types didn't change if the upper byte of the type number was zero. So I assumed that meant "fixed". But the most recent firmware update made clear that no, they aren't fixed, and the upper byte of the type must be some other thing. This moves some more of the parsing over to comparing the strings, rather than looking at the type index. It still leaves the sample data alone, and I really want to do something more efficient than comparing the type descriptor string for that, but at least the dive header fields are now just comparing strings. The actual marshalling that Suunto uses also describes the encoding, and it's all ignoring that for now. Signed-off-by: Linus Torvalds --- src/suunto_eonsteel_parser.c | 173 +++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 67 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 2998bdb..ce6d01a 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -605,30 +605,41 @@ static void set_depth_field(suunto_eonsteel_parser_t *eon, unsigned short d) } } -// gas type: 0=Off,1=Primary,2=?,3=Diluent -static void add_gas_type(suunto_eonsteel_parser_t *eon, unsigned char type) +// new gas: +// "sml.DeviceLog.Header.Diving.Gases+Gas.State" +// +// We eventually need to parse the descriptor for that 'enum type'. +// Two versions so far: +// "enum:0=Off,1=Primary,2=?,3=Diluent" +// "enum:0=Off,1=Primary,3=Diluent,4=Oxygen" +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++; eon->cache.initialized |= 1 << DC_FIELD_GASMIX_COUNT; + return 0; } +// "sml.DeviceLog.Header.Diving.Gases.Gas.Oxygen" // O2 percentage as a byte -static void add_gas_o2(suunto_eonsteel_parser_t *eon, unsigned char o2) +static int add_gas_o2(suunto_eonsteel_parser_t *eon, unsigned char o2) { int idx = eon->cache.ngases-1; if (idx >= 0) eon->cache.gasmix[idx].oxygen = o2 / 100.0; eon->cache.initialized |= 1 << DC_FIELD_GASMIX; + return 0; } +// "sml.DeviceLog.Header.Diving.Gases.Gas.Helium" // He percentage as a byte -static void add_gas_he(suunto_eonsteel_parser_t *eon, unsigned char he) +static int add_gas_he(suunto_eonsteel_parser_t *eon, unsigned char he) { int idx = eon->cache.ngases-1; if (idx >= 0) eon->cache.gasmix[idx].helium = he / 100.0; eon->cache.initialized |= 1 << DC_FIELD_GASMIX; + return 0; } static float get_le32_float(const unsigned char *src) @@ -650,57 +661,71 @@ static float get_le32_float(const unsigned char *src) // Info.SW // Name // SerialNumber -static int traverse_device_fields(suunto_eonsteel_parser_t *eon, const char *name, const void *data, int len) +static int traverse_device_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.Device."); + return 0; } -// "Header" fields are: -// Activity (utf8) -// DateTime (utf8) -// Depth.Avg (float32,precision=2) -// Depth.Max (float32,precision=2) -// Diving.AlgorithmAscentTime (uint32) -// Diving.AlgorithmBottomMixture.Helium (uint8,precision=2) (0.01*x,100*x) -// Diving.AlgorithmBottomMixture.Oxygen (uint8,precision=2) (0.01*x,100*x) -// Diving.AlgorithmBottomTime (uint32) -// Diving.AlgorithmTransitionDepth (uint8) -// Diving.Algorithm (utf8) -// Diving.Altitude (uint16) -// Diving.Conservatism (int8) -// Diving.DaysInSeries (uint32) -// Diving.DesaturationTime (uint32) -// Diving.DiveMode (utf8) -// Diving.EndTissue.CNS (float32,precision=3) -// Diving.EndTissue.Helium+Pressure (uint32) -// Diving.EndTissue.Nitrogen+Pressure (uint32) -// Diving.EndTissue.OLF (float32,precision=3) -// Diving.EndTissue.OTU (float32) -// Diving.EndTissue.RgbmHelium (float32,precision=3) -// Diving.EndTissue.RgbmNitrogen (float32,precision=3) -// Diving.NumberInSeries (uint32) -// Diving.PreviousDiveDepth (float32,precision=2) -// Diving.StartTissue.CNS (float32,precision=3) -// Diving.StartTissue.Helium+Pressure (uint32) -// Diving.StartTissue.Nitrogen+Pressure (uint32) -// Diving.StartTissue.OLF (float32,precision=3) -// Diving.StartTissue.OTU (float32) -// Diving.StartTissue.RgbmHelium (float32,precision=3) -// Diving.StartTissue.RgbmNitrogen (float32,precision=3) -// Diving.SurfacePressure (uint32) -// Diving.SurfaceTime (uint32) -// Duration (uint32) -// PauseDuration (uint32) -// SampleInterval (uint8) -static int traverse_header_fields(suunto_eonsteel_parser_t *eon, const char *name, const void *data, int len) +// "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) +// SurfacePressure (uint32) +// Conservatism (int8) +// Altitude (uint16) +// AlgorithmTransitionDepth (uint8) +// DaysInSeries (uint32) +// PreviousDiveDepth (float32,precision=2) +// StartTissue.CNS (float32,precision=3) +// StartTissue.OTU (float32) +// StartTissue.OLF (float32,precision=3) +// StartTissue.Nitrogen+Pressure (uint32) +// StartTissue.Helium+Pressure (uint32) +// StartTissue.RgbmNitrogen (float32,precision=3) +// StartTissue.RgbmHelium (float32,precision=3) +// DiveMode (utf8) +// AlgorithmBottomTime (uint32) +// AlgorithmAscentTime (uint32) +// AlgorithmBottomMixture.Oxygen (uint8,precision=2) +// AlgorithmBottomMixture.Helium (uint8,precision=2) +// DesaturationTime (uint32) +// EndTissue.CNS (float32,precision=3) +// EndTissue.OTU (float32) +// EndTissue.OLF (float32,precision=3) +// EndTissue.Nitrogen+Pressure (uint32) +// EndTissue.Helium+Pressure (uint32) +// EndTissue.RgbmNitrogen (float32,precision=3) +// EndTissue.RgbmHelium (float32,precision=3) +static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, + const unsigned char *data, int len) { - if (!strcmp(name, "Depth.Max")) { - double d = get_le32_float(data); - if (d > eon->cache.maxdepth) - eon->cache.maxdepth = d; - return 0; - } - if (!strcmp(name, "Diving.SurfacePressure")) { + 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, "SurfacePressure")) { unsigned int pressure = array_uint32_le(data); // in SI units - Pascal eon->cache.surface_pressure = pressure / 100000.0; // bar eon->cache.initialized |= 1 << DC_FIELD_ATMOSPHERIC; @@ -710,7 +735,34 @@ static int traverse_header_fields(suunto_eonsteel_parser_t *eon, const char *nam return 0; } -static int traverse_dynamic_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, const void *data, int len) +// "Header" fields are: +// Activity (utf8) +// DateTime (utf8) +// Depth.Avg (float32,precision=2) +// Depth.Max (float32,precision=2) +// Diving.* +// Duration (uint32) +// PauseDuration (uint32) +// SampleInterval (uint8) +static int traverse_header_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."); + + if (!strncmp(name, "Diving.", 7)) + return traverse_diving_fields(eon, desc, data, len); + + if (!strcmp(name, "Depth.Max")) { + double d = get_le32_float(data); + if (d > eon->cache.maxdepth) + eon->cache.maxdepth = d; + return 0; + } + + return 0; +} + +static int traverse_dynamic_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, const unsigned char *data, int len) { const char *name = desc->desc; @@ -719,9 +771,9 @@ static int traverse_dynamic_fields(suunto_eonsteel_parser_t *eon, const struct t if (!strncmp(name, "DeviceLog.", 10)) { name += 10; if (!strncmp(name, "Device.", 7)) - return traverse_device_fields(eon, name+7, data, len); + return traverse_device_fields(eon, desc, data, len); if (!strncmp(name, "Header.", 7)) { - return traverse_header_fields(eon, name+7, data, len); + return traverse_header_fields(eon, desc, data, len); } } } @@ -743,21 +795,8 @@ static int traverse_fields(unsigned short type, const struct type_desc *desc, co case 0x0003: // depth in first word set_depth_field(eon, array_uint16_le(data)); break; - case 0x000d: // gas state in first byte - add_gas_type(eon, data[0]); - break; - case 0x000e: // Oxygen percentage in first byte - add_gas_o2(eon, data[0]); - break; - case 0x000f: // Helium percentage in first byte - add_gas_he(eon, data[0]); - break; default: - // The types with the high byte set seem to be dynamic - // although not all of them seem to change. But let's - // just check the descriptor name for them. - if (type > 255) - traverse_dynamic_fields(eon, desc, data, len); + traverse_dynamic_fields(eon, desc, data, len); break; } return 0; From edbdea04727ee6f1d841dd94e21ccda0c00437df Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 8 Jun 2015 15:21:02 -0700 Subject: [PATCH 7/9] EON Steel: convert to dynamic sample parsing This gets rid of the fixed sample indexes and the hardcoded grouping code, and replaces them with proper parsing of the type descriptions. This should mean that the new Suunto firmware v1.1.15 is now fully supported by libdivecomputer. There are still parts of the event description that we should really parse better, notably the 'enum' descriptions of what the different enumerated types mean, because it looks like those will change too. But that is not nearly as important as getting the basic infrastructure done for the core sample types. Almost accidentally, this also ends up now parsing the compass heading event. Signed-off-by: Linus Torvalds --- src/suunto_eonsteel_parser.c | 474 +++++++++++++++++++++++++++++------ 1 file changed, 399 insertions(+), 75 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index ce6d01a..d65b338 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -21,6 +21,7 @@ #include #include +#include #include @@ -28,8 +29,39 @@ #include "parser-private.h" #include "array.h" + +#define C_ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) + +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_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_alarm, + ES_alarm_active, + ES_gasswitch, // uint16 + ES_bookmark, +}; + +#define EON_MAX_GROUP 16 + struct type_desc { const char *desc, *format, *mod; + unsigned int size; + enum eon_sample type[EON_MAX_GROUP]; }; #define MAXTYPE 512 @@ -53,6 +85,165 @@ 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); +static const struct { + const char *name; + enum eon_sample type; +} type_translation[] = { + { "Depth", ES_depth }, + { "Temperature", ES_temp }, + { "NoDecTime", ES_ndl }, + { "Ceiling", ES_ceiling }, + { "TimeToSurface", ES_tts }, + { "Heading", ES_heading }, + { "DeviceInternalAbsPressure", ES_abspressure }, + { "GasTime", ES_none }, + { "Ventilation", ES_none }, + { "Cylinders+Cylinder.GasNumber", ES_gasnr }, + { "Cylinders.Cylinder.Pressure", ES_pressure }, + { "Events+State.Type", ES_state }, + { "Events.State.Active", ES_state_active }, + { "Events+Notify.Type", ES_notify }, + { "Events.Notify.Active", ES_notify_active }, + { "Events+Warning.Type", ES_warning }, + { "Events.Warning.Active", ES_warning_active }, + { "Events+Alarm.Type", ES_alarm }, + { "Events.Alarm.Active", ES_alarm_active }, + { "Events.Bookmark.Name", ES_bookmark }, + { "Events.GasSwitch.GasNumber", ES_gasswitch }, + { "Events.DiveTimer.Active", ES_none }, + { "Events.DiveTimer.Time", ES_none }, +}; + +static enum eon_sample lookup_descriptor_type(suunto_eonsteel_parser_t *eon, struct type_desc *desc) +{ + int i; + const char *name = desc->desc; + + // Not a sample type? Skip it + if (strncmp(name, "sml.DeviceLog.Samples", 21)) + return ES_none; + + // Skip the common base + name += 21; + + // We have a "+Sample.Time", which starts a new + // sample and contains the time delta + if (!strcmp(name, "+Sample.Time")) + return ES_dtime; + + // .. the rest should start with ".Sample." + if (strncmp(name, ".Sample.", 8)) + return ES_none; + + // Skip the ".Sample." + name += 8; + + // .. and look it up in the table of sample type strings + for (i = 0; i < C_ARRAY_SIZE(type_translation); i++) { + if (!strcmp(name, type_translation[i].name)) + return type_translation[i].type; + } + return ES_none; +} + +static int lookup_descriptor_size(suunto_eonsteel_parser_t *eon, struct type_desc *desc) +{ + const char *format = desc->format; + unsigned char c; + + if (!format) + return 0; + + if (!strncmp(format, "bool", 4)) + return 1; + if (!strncmp(format, "enum", 4)) + return 1; + if (!strncmp(format, "utf8", 4)) + return 0; + + // find the byte size (eg "float32" -> 4 bytes) + while ((c = *format) != 0) { + if (isdigit(c)) + return atoi(format)/8; + format++; + } + return 0; +} + +static int fill_in_group_details(suunto_eonsteel_parser_t *eon, struct type_desc *desc) +{ + int subtype = 0; + const char *grp = desc->desc; + + for (;;) { + struct type_desc *base; + char *end; + long index; + + index = strtol(grp, &end, 10); + if (index < 0 || index > MAXTYPE || end == grp) { + ERROR(eon->base.context, "Group type descriptor '%s' does not parse", desc->desc); + break; + } + base = eon->type_desc + index; + if (!base->desc) { + ERROR(eon->base.context, "Group type descriptor '%s' has undescribed index %d", desc->desc, index); + break; + } + if (!base->size) { + ERROR(eon->base.context, "Group type descriptor '%s' uses unsized sub-entry '%s'", desc->desc, base->desc); + break; + } + if (!base->type[0]) { + ERROR(eon->base.context, "Group type descriptor '%s' has non-enumerated sub-entry '%s'", desc->desc, base->desc); + break; + } + if (base->type[1]) { + ERROR(eon->base.context, "Group type descriptor '%s' has a recursive group sub-entry '%s'", desc->desc, base->desc); + break; + } + if (subtype >= EON_MAX_GROUP-1) { + ERROR(eon->base.context, "Group type descriptor '%s' has too many sub-entries", desc->desc); + break; + } + desc->size += base->size; + desc->type[subtype++] = base->type[0]; + switch (*end) { + case 0: + return 0; + case ',': + grp = end+1; + continue; + default: + ERROR(eon->base.context, "Group type descriptor '%s' has undescribed index %d", desc->desc, index); + return -1; + } + } +} + +/* + * Here we cache descriptor data so that we don't have + * to re-parse the string all the time. That way we can + * do it just once per type. + * + * Right now we only bother with the sample descriptors, + * which all start with "sml.DeviceLog.Samples" (for the + * base types) or are "GRP" types that are a group of said + * types and are a set of numbers. + */ +static int fill_in_desc_details(suunto_eonsteel_parser_t *eon, struct type_desc *desc) +{ + if (!desc->desc) + return 0; + + if (isdigit(desc->desc[0])) + return fill_in_group_details(eon, desc); + + desc->size = lookup_descriptor_size(eon, desc); + desc->type[0] = lookup_descriptor_type(eon, desc); + return 0; +} + static void desc_free (struct type_desc desc[], unsigned int count) { @@ -68,7 +259,7 @@ static int record_type(suunto_eonsteel_parser_t *eon, unsigned short type, const struct type_desc desc; const char *next; - desc.desc = desc.format = desc.mod = NULL; + memset(&desc, 0, sizeof(desc)); do { int len; char *p; @@ -126,6 +317,8 @@ static int record_type(suunto_eonsteel_parser_t *eon, unsigned short type, const return -1; } + fill_in_desc_details(eon, &desc); + desc_free(eon->type_desc + type, 1); eon->type_desc[type] = desc; return 0; @@ -230,6 +423,11 @@ struct sample_data { unsigned int time; unsigned char state_type, notify_type; unsigned char warning_type, alarm_type; + + /* We gather up deco and cylinder pressure information */ + int gasnr; + int tts, ndl; + double ceiling; }; static void sample_time(struct sample_data *info, unsigned short time_delta) @@ -263,32 +461,60 @@ static void sample_temp(struct sample_data *info, short temp) if (info->callback) info->callback(DC_SAMPLE_TEMPERATURE, sample, info->userdata); } -static void sample_deco(struct sample_data *info, short ndl, unsigned short tts, unsigned ceiling) +static void sample_ndl(struct sample_data *info, short ndl) { dc_sample_value_t sample = {0}; - /* Are we in deco? */ - if (ndl < 0) { - sample.deco.type = DC_DECO_DECOSTOP; - if (tts != 0xffff) - sample.deco.time = tts; - if (ceiling != 0xffff) - sample.deco.depth = ceiling / 100.0; - } else { - sample.deco.type = DC_DECO_NDL; - sample.deco.time = ndl; - } + info->ndl = ndl; + if (ndl < 0) + return; + + sample.deco.type = DC_DECO_NDL; + sample.deco.time = ndl; if (info->callback) info->callback(DC_SAMPLE_DECO, sample, info->userdata); } -static void sample_cylinder_pressure(struct sample_data *info, unsigned char idx, unsigned short pressure) +static void sample_tts(struct sample_data *info, unsigned short tts) +{ + if (tts != 0xffff) + info->tts = tts; +} + +static void sample_ceiling(struct sample_data *info, unsigned short ceiling) +{ + if (ceiling != 0xffff) + info->ceiling = ceiling / 100.0; +} + +static void sample_heading(struct sample_data *info, unsigned short heading) +{ + dc_sample_value_t sample = {0}; + + if (heading == 0xffff) + return; + + sample.event.type = SAMPLE_EVENT_HEADING; + sample.event.value = heading; + if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); +} + +static void sample_abspressure(struct sample_data *info, unsigned short pressure) +{ +} + +static void sample_gasnr(struct sample_data *info, unsigned char idx) +{ + info->gasnr = idx; +} + +static void sample_pressure(struct sample_data *info, unsigned short pressure) { dc_sample_value_t sample = {0}; if (pressure == 0xffff) return; - sample.pressure.tank = idx-1; + sample.pressure.tank = info->gasnr-1; sample.pressure.value = pressure / 100.0; if (info->callback) info->callback(DC_SAMPLE_PRESSURE, sample, info->userdata); } @@ -338,6 +564,8 @@ static void sample_gas_switch_event(struct sample_data *info, unsigned short idx * 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(struct sample_data *info, unsigned char type) { @@ -364,6 +592,7 @@ static void sample_event_notify_type(struct sample_data *info, unsigned char typ } +// FIXME! This needs to parse the actual type descriptor enum static void sample_event_notify_value(struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; @@ -441,6 +670,7 @@ static void sample_event_alarm_type(struct sample_data *info, unsigned char type } +// FIXME! This needs to parse the actual type descriptor enum static void sample_event_alarm_value(struct sample_data *info, unsigned char value) { dc_sample_value_t sample = {0}; @@ -465,58 +695,134 @@ static void sample_event_alarm_value(struct sample_data *info, unsigned char val if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); } +static int handle_sample_type(struct sample_data *info, enum eon_sample type, const unsigned char *data) +{ + switch (type) { + case ES_dtime: + sample_time(info, array_uint16_le(data)); + return 2; + + case ES_depth: + sample_depth(info, array_uint16_le(data)); + return 2; + + case ES_temp: + sample_temp(info, array_uint16_le(data)); + return 2; + + case ES_ndl: + sample_ndl(info, array_uint16_le(data)); + return 2; + + case ES_ceiling: + sample_ceiling(info, array_uint16_le(data)); + return 2; + + case ES_tts: + sample_tts(info, array_uint16_le(data)); + return 2; + + case ES_heading: + sample_heading(info, array_uint16_le(data)); + return 2; + + case ES_abspressure: + sample_abspressure(info, array_uint16_le(data)); + return 2; + + case ES_gasnr: + sample_gasnr(info, *data); + return 1; + + case ES_pressure: + sample_pressure(info, array_uint16_le(data)); + return 2; + + case ES_state: + sample_event_state_type(info, data[0]); + return 1; + + case ES_state_active: + sample_event_state_value(info, data[0]); + return 1; + + case ES_notify: + sample_event_notify_type(info, data[0]); + return 1; + + case ES_notify_active: + sample_event_notify_value(info, data[0]); + return 1; + + case ES_warning: + sample_event_warning_type(info, data[0]); + return 1; + + case ES_warning_active: + sample_event_warning_value(info, data[0]); + return 1; + + case ES_alarm: + sample_event_alarm_type(info, data[0]); + return 1; + + case ES_alarm_active: + sample_event_alarm_value(info, data[0]); + return 1; + + case ES_bookmark: + sample_bookmark_event(info, array_uint16_le(data)); + return 2; + + case ES_gasswitch: + sample_gas_switch_event(info, array_uint16_le(data)); + return 2; + + default: + return 0; + } +} static int traverse_samples(unsigned short type, const struct type_desc *desc, const unsigned char *data, int len, void *user) { struct sample_data *info = (struct sample_data *) user; + suunto_eonsteel_parser_t *eon = info->eon; + int i, used = 0; - switch (type) { - case 0x0001: // group: time in first word, depth in second - sample_time(info, array_uint16_le(data)); - sample_depth(info, array_uint16_le(data+2)); - sample_temp(info, array_uint16_le(data+4)); - sample_deco(info, array_uint16_le(data+8), array_uint16_le(data+10), array_uint16_le(data+12)); - break; - case 0x0002: // time in first word - sample_time(info, array_uint16_le(data)); - break; - case 0x0003: // depth in first word - sample_depth(info, array_uint16_le(data)); - break; - case 0x000a: // cylinder idx in first byte, pressure in next word - sample_cylinder_pressure(info, data[0], array_uint16_le(data+1)); - break; - case 0x0013: - sample_event_state_type(info, data[0]); - break; - case 0x0014: - sample_event_state_value(info, data[0]); - break; - case 0x0015: - sample_event_notify_type(info, data[0]); - break; - case 0x0016: - sample_event_notify_value(info, data[0]); - break; - case 0x0017: - sample_event_warning_type(info, data[0]); - break; - case 0x0018: - sample_event_warning_value(info, data[0]); - break; - case 0x0019: - sample_event_alarm_type(info, data[0]); - break; - case 0x001a: - sample_event_alarm_value(info, data[0]); - break; - case 0x001c: - sample_bookmark_event(info, array_uint16_le(data)); - break; - case 0x001d: - sample_gas_switch_event(info, array_uint16_le(data)); - break; + if (desc->size > len) + ERROR(eon->base.context, "Got %d bytes of data for '%s' that wants %d bytes", len, desc->desc, desc->size); + + info->ndl = -1; + info->tts = 0; + info->ceiling = 0.0; + + for (i = 0; i < EON_MAX_GROUP; i++) { + enum eon_sample type = desc->type[i]; + int bytes = handle_sample_type(info, type, data); + + if (!bytes) + break; + if (bytes > len) { + ERROR(eon->base.context, "Wanted %d bytes of data, only had %d bytes ('%s' idx %d)", bytes, len, desc->desc, i); + break; + } + data += bytes; + len -= bytes; + used += bytes; } + + if (info->ndl < 0 && (info->tts || info->ceiling)) { + dc_sample_value_t sample = {0}; + + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.time = info->tts; + sample.deco.depth = info->ceiling; + if (info->callback) info->callback(DC_SAMPLE_DECO, sample, info->userdata); + } + + // Warn if there are left-over bytes for something we did use part of + if (used && len) + ERROR(eon->base.context, "Entry for '%s' had %d bytes, only used %d", len+used, used); return 0; } @@ -780,25 +1086,43 @@ static int traverse_dynamic_fields(suunto_eonsteel_parser_t *eon, const struct t return 0; } +/* + * This is a simplified sample parser that only parses the depth and time + * samples. It also depends on the GRP entries always starting with time/depth, + * and just stops on anything else. + */ +static int traverse_sample_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, const unsigned char *data, int len) +{ + int i; + + for (i = 0; i < EON_MAX_GROUP; i++) { + enum eon_sample type = desc->type[i]; + + switch (type) { + case ES_dtime: + add_time_field(eon, array_uint16_le(data)); + data += 2; + continue; + case ES_depth: + set_depth_field(eon, array_uint16_le(data)); + data += 2; + continue; + } + break; + } + return 0; +} + static int traverse_fields(unsigned short type, const struct type_desc *desc, const unsigned char *data, int len, void *user) { suunto_eonsteel_parser_t *eon = (suunto_eonsteel_parser_t *) user; - switch (type) { - case 0x0001: // group: time in first word, depth in second - add_time_field(eon, array_uint16_le(data)); - set_depth_field(eon, array_uint16_le(data+2)); - break; - case 0x0002: // time in first word - add_time_field(eon, array_uint16_le(data)); - break; - case 0x0003: // depth in first word - set_depth_field(eon, array_uint16_le(data)); - break; - default: + // Sample type? Do basic maxdepth and time parsing + if (desc->type[0]) + traverse_sample_fields(eon, desc, data, len); + else traverse_dynamic_fields(eon, desc, data, len); - break; - } + return 0; } From a16f8ec47094a99cc45ea2f6a698c8341a4e69b1 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 14 Jun 2015 14:20:05 -1000 Subject: [PATCH 8/9] EON Steel: fix up missing gastime/ventilation parsing Now that I actually have dives with the new format and with air integration, I could test it all out and noticed some missing pieces. This adds parsing for gastime and ventilation events (although we don't *do* anything with the ventilation data, I have no idea what the number means). Also, this fixes an annoying warning message problem, which caused these missing events to cause a SIGSEGV rather than just a bening warning. Stupid bug, and only went unnoticed because libdivecomputer isn't built with format string warnings. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 42 ++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index d65b338..156bf28 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -42,6 +42,8 @@ enum eon_sample { 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, @@ -96,8 +98,8 @@ static const struct { { "TimeToSurface", ES_tts }, { "Heading", ES_heading }, { "DeviceInternalAbsPressure", ES_abspressure }, - { "GasTime", ES_none }, - { "Ventilation", ES_none }, + { "GasTime", ES_gastime }, + { "Ventilation", ES_ventilation }, { "Cylinders+Cylinder.GasNumber", ES_gasnr }, { "Cylinders.Cylinder.Pressure", ES_pressure }, { "Events+State.Type", ES_state }, @@ -215,10 +217,11 @@ static int fill_in_group_details(suunto_eonsteel_parser_t *eon, struct type_desc grp = end+1; continue; default: - ERROR(eon->base.context, "Group type descriptor '%s' has undescribed index %d", desc->desc, index); + ERROR(eon->base.context, "Group type descriptor '%s' has unparseable index %d", desc->desc, index); return -1; } } + return -1; } /* @@ -502,6 +505,29 @@ static void sample_abspressure(struct sample_data *info, unsigned short pressure { } +static void sample_gastime(struct sample_data *info, short gastime) +{ + dc_sample_value_t sample = {0}; + + if (gastime < 0) + return; + + sample.event.type = SAMPLE_EVENT_AIRTIME; + sample.event.value = gastime; + if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); +} + +/* + * Per-sample "ventilation" data. + * + * It's described as: + * - "uint16,precision=6,nillable=65535" + * - "x/6000000,x" + */ +static void sample_ventilation(struct sample_data *info, unsigned short unk) +{ +} + static void sample_gasnr(struct sample_data *info, unsigned char idx) { info->gasnr = idx; @@ -730,6 +756,14 @@ static int handle_sample_type(struct sample_data *info, enum eon_sample type, co sample_abspressure(info, array_uint16_le(data)); return 2; + case ES_gastime: + sample_gastime(info, array_uint16_le(data)); + return 2; + + case ES_ventilation: + sample_ventilation(info, array_uint16_le(data)); + return 2; + case ES_gasnr: sample_gasnr(info, *data); return 1; @@ -822,7 +856,7 @@ static int traverse_samples(unsigned short type, const struct type_desc *desc, c // Warn if there are left-over bytes for something we did use part of if (used && len) - ERROR(eon->base.context, "Entry for '%s' had %d bytes, only used %d", len+used, used); + ERROR(eon->base.context, "Entry for '%s' had %d bytes, only used %d", desc->desc, len+used, used); return 0; } From 90a19dd154b38521a99259a50b7ce7199533941a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 14 Jun 2015 18:56:16 -1000 Subject: [PATCH 9/9] EON Steel: don't report airtime remaining as a libdivecomputer event It looks like the SAMPLE_EVENT_AIRTIME is actually meant for just airtime warnings (as in the dive computer warning about low air). If we ever care, I think we'd need to add a new interface for this. Signed-off-by: Linus Torvalds Signed-off-by: Dirk Hohndel --- src/suunto_eonsteel_parser.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/suunto_eonsteel_parser.c b/src/suunto_eonsteel_parser.c index 156bf28..10577b2 100644 --- a/src/suunto_eonsteel_parser.c +++ b/src/suunto_eonsteel_parser.c @@ -512,9 +512,7 @@ static void sample_gastime(struct sample_data *info, short gastime) if (gastime < 0) return; - sample.event.type = SAMPLE_EVENT_AIRTIME; - sample.event.value = gastime; - if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); + // Hmm. We have no good way to report airtime remaining } /*