From c4cbd19d5b439551b44e94443c7728e95f3d4896 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 17 Mar 2015 21:25:19 +0100 Subject: [PATCH 1/9] Resend the command on failures. --- src/cressi_edy.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/cressi_edy.c b/src/cressi_edy.c index 1de3b53..d9bf813 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -39,6 +39,8 @@ rc == -1 ? DC_STATUS_IO : DC_STATUS_TIMEOUT \ ) +#define MAXRETRIES 4 + #define SZ_MEMORY 0x8000 #define SZ_PACKET 0x80 #define SZ_PAGE (SZ_PACKET / 4) @@ -77,7 +79,7 @@ static const dc_device_vtable_t cressi_edy_device_vtable = { static dc_status_t -cressi_edy_transfer (cressi_edy_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, int trailer) +cressi_edy_packet (cressi_edy_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, int trailer) { dc_device_t *abstract = (dc_device_t *) device; @@ -86,13 +88,6 @@ cressi_edy_transfer (cressi_edy_device_t *device, const unsigned char command[], if (device_is_cancelled (abstract)) return DC_STATUS_CANCELLED; - // Flush the serial input buffer. - int rc = serial_flush (device->port, SERIAL_QUEUE_INPUT); - if (rc == -1) { - ERROR (abstract->context, "Failed to flush the serial input buffer."); - return DC_STATUS_IO; - } - // Send the command to the device. int n = serial_write (device->port, command, csize); if (n != csize) { @@ -122,6 +117,26 @@ cressi_edy_transfer (cressi_edy_device_t *device, const unsigned char command[], return DC_STATUS_SUCCESS; } +static dc_status_t +cressi_edy_transfer (cressi_edy_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, int trailer) +{ + unsigned int nretries = 0; + dc_status_t rc = DC_STATUS_SUCCESS; + while ((rc = cressi_edy_packet (device, command, csize, answer, asize, trailer)) != DC_STATUS_SUCCESS) { + if (rc != DC_STATUS_TIMEOUT && rc != DC_STATUS_PROTOCOL) + return rc; + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) + return rc; + + // Delay the next attempt. + serial_sleep (device->port, 300); + serial_flush (device->port, SERIAL_QUEUE_INPUT); + } + + return DC_STATUS_SUCCESS; +} static dc_status_t cressi_edy_init1 (cressi_edy_device_t *device) From edd777dbd3080490dcd0797843f4f001a9d57a50 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 19 Mar 2015 20:17:31 +0100 Subject: [PATCH 2/9] Wait after changing the serial line settings. Without the delay, the communication immediately fails. We receive the command echo, but not the actual data packet. I suspect the device is still be busy with the initialization and needs a bit more time before it's ready to accept a request. --- src/cressi_edy.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cressi_edy.c b/src/cressi_edy.c index d9bf813..5a51090 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -239,6 +239,10 @@ cressi_edy_device_open (dc_device_t **out, dc_context_t *context, const char *na return DC_STATUS_IO; } + // Make sure everything is in a sane state. + serial_sleep(device->port, 300); + serial_flush(device->port, SERIAL_QUEUE_BOTH); + // Send the init commands. cressi_edy_init1 (device); cressi_edy_init2 (device); @@ -253,6 +257,10 @@ cressi_edy_device_open (dc_device_t **out, dc_context_t *context, const char *na return DC_STATUS_IO; } + // Make sure everything is in a sane state. + serial_sleep(device->port, 300); + serial_flush(device->port, SERIAL_QUEUE_BOTH); + *out = (dc_device_t*) device; return DC_STATUS_SUCCESS; From c4233c091d6b24ec75c30c9ef38676cdbc9f1dfe Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 19 Mar 2015 20:23:47 +0100 Subject: [PATCH 3/9] Send the command and read the echo byte by byte. When trying to send a command, the first attempt always fails. We receive the echo, but no data packet. A second attempt usually works, but we always get back the same data packet. That's cleary wrong. Now, when comparing the data packets with those of the Tusa application, I noticed something very interesting. When we request the first packet (page 0x0000), we get: W: 520000 R: 520000 R: 00880124056202000250002890470824...19202720002000200020002000204145 The Tusa application also request this page, but the response is completely different: W: 520000 R: 520000 R: 22182224222322092203220522112210...0000000000f021fc0000000000000045 The response we get is identical to the response that the Tusa application gets for page 0x0052: W: 520052 R: 520052 R: 00880124056202000250002890470824...19202720002000200020002000204145 The only difference here is the echo of the command. But the echo should be ignored, because it's generated by the pc interface, and not send by the dive computer. This is easily verified by the fact that we always receive an echo, even without a dive computer connected (e.g. only the pc interface). Notice how the command type (first byte) and page number (last byte) are identical (0x52) for this request! I suspect that somehow the command type ends up being interpreted as the page number. That would explain why we're always getting the same response: as far as the device is concerned we're always requesting page 0x52. This is probably also related to the fact that the device doesn't respond after the first request. It's not impossible that if the first command wasn't received correctly and we resend the command, the device receives something that contains parts of both attempts. By sending the command and reading the echo byte by byte instead of all at once, the above problem disappears. --- src/cressi_edy.c | 71 +++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/src/cressi_edy.c b/src/cressi_edy.c index 5a51090..fc1e96b 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -83,35 +83,45 @@ cressi_edy_packet (cressi_edy_device_t *device, const unsigned char command[], u { dc_device_t *abstract = (dc_device_t *) device; - assert (asize >= csize); - if (device_is_cancelled (abstract)) return DC_STATUS_CANCELLED; - // Send the command to the device. - int n = serial_write (device->port, command, csize); - if (n != csize) { - ERROR (abstract->context, "Failed to send the command."); - return EXITCODE (n); + for (unsigned int i = 0; i < csize; ++i) { + // Send the command to the device. + int n = serial_write (device->port, command + i, 1); + if (n != 1) { + ERROR (abstract->context, "Failed to send the command."); + return EXITCODE (n); + } + + // Receive the echo. + unsigned char echo = 0; + n = serial_read (device->port, &echo, 1); + if (n != 1) { + ERROR (abstract->context, "Failed to receive the echo."); + return EXITCODE (n); + } + + // Verify the echo. + if (command[i] != echo) { + ERROR (abstract->context, "Unexpected echo."); + return DC_STATUS_PROTOCOL; + } } - // Receive the answer of the device. - n = serial_read (device->port, answer, asize); - if (n != asize) { - ERROR (abstract->context, "Failed to receive the answer."); - return EXITCODE (n); - } + if (asize) { + // Receive the answer of the device. + int n = serial_read (device->port, answer, asize); + if (n != asize) { + ERROR (abstract->context, "Failed to receive the answer."); + return EXITCODE (n); + } - // Verify the echo. - if (memcmp (answer, command, csize) != 0) { - ERROR (abstract->context, "Unexpected echo."); - return DC_STATUS_PROTOCOL; - } - - // Verify the trailer of the packet. - if (trailer && answer[asize - 1] != 0x45) { - ERROR (abstract->context, "Unexpected answer trailer byte."); - return DC_STATUS_PROTOCOL; + // Verify the trailer of the packet. + if (trailer && answer[asize - 1] != 0x45) { + ERROR (abstract->context, "Unexpected answer trailer byte."); + return DC_STATUS_PROTOCOL; + } } return DC_STATUS_SUCCESS; @@ -142,7 +152,7 @@ static dc_status_t cressi_edy_init1 (cressi_edy_device_t *device) { unsigned char command[3] = {0x41, 0x42, 0x43}; - unsigned char answer[6] = {0}; + unsigned char answer[3] = {0}; return cressi_edy_transfer (device, command, sizeof (command), answer, sizeof (answer), 0); } @@ -152,13 +162,13 @@ static dc_status_t cressi_edy_init2 (cressi_edy_device_t *device) { unsigned char command[1] = {0x44}; - unsigned char answer[2] = {0}; + unsigned char answer[1] = {0}; dc_status_t rc = cressi_edy_transfer (device, command, sizeof (command), answer, sizeof (answer), 0); if (rc != DC_STATUS_SUCCESS) return rc; - device->model = answer[1]; + device->model = answer[0]; return DC_STATUS_SUCCESS; } @@ -168,7 +178,7 @@ static dc_status_t cressi_edy_init3 (cressi_edy_device_t *device) { unsigned char command[1] = {0x0C}; - unsigned char answer[2] = {0}; + unsigned char answer[1] = {0}; return cressi_edy_transfer (device, command, sizeof (command), answer, sizeof (answer), 1); } @@ -178,9 +188,8 @@ static dc_status_t cressi_edy_quit (cressi_edy_device_t *device) { unsigned char command[1] = {0x46}; - unsigned char answer[1] = {0}; - return cressi_edy_transfer (device, command, sizeof (command), answer, sizeof (answer), 0); + return cressi_edy_transfer (device, command, sizeof (command), NULL, 0, 0); } @@ -301,7 +310,7 @@ cressi_edy_device_read (dc_device_t *abstract, unsigned int address, unsigned ch while (nbytes < size) { // Read the package. unsigned int number = address / SZ_PAGE; - unsigned char answer[3 + SZ_PACKET + 1] = {0}; + unsigned char answer[SZ_PACKET + 1] = {0}; unsigned char command[3] = {0x52, (number >> 8) & 0xFF, // high (number ) & 0xFF}; // low @@ -309,7 +318,7 @@ cressi_edy_device_read (dc_device_t *abstract, unsigned int address, unsigned ch if (rc != DC_STATUS_SUCCESS) return rc; - memcpy (data, answer + 3, SZ_PACKET); + memcpy (data, answer, SZ_PACKET); nbytes += SZ_PACKET; address += SZ_PACKET; From ad6ec1e7f50ea41a0b67ba94e787039ab4ce39ae Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 19 Mar 2015 21:57:34 +0100 Subject: [PATCH 4/9] Align all read operations to packet boundaries. Packets have a fixed size of 0x80 bytes, while a single page is only 0x20 bytes long. Thus each read operation always returns 4 pages at once. Now, if the end-of-profile pointer is not nicely aligned on a packet boundary, then the download algorithm won't arrive exactly at the start address of the ringbuffer, because the ringbuffer is properly aligned. The consequence is that we won't even notice we reached the ringbuffer boundary and happily continue reading outside the ringbuffer. Oops! This is fixed by aligning the end-of-profile pointer, which guarantees that all read operations are now nicely aligned to packet boundaries. --- src/cressi_edy.c | 104 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 16 deletions(-) diff --git a/src/cressi_edy.c b/src/cressi_edy.c index fc1e96b..4b58743 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -77,6 +77,19 @@ static const dc_device_vtable_t cressi_edy_device_vtable = { cressi_edy_device_close /* close */ }; +static unsigned int +ifloor (unsigned int x, unsigned int n) +{ + // Round down to next lower multiple. + return (x / n) * n; +} + +static unsigned int +iceil (unsigned int x, unsigned int n) +{ + // Round up to next higher multiple. + return ((x + n - 1) / n) * n; +} static dc_status_t cressi_edy_packet (cressi_edy_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, int trailer) @@ -387,10 +400,6 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v return rc; } - // Update and emit a progress event. - progress.current += SZ_PACKET; - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - // Get the logbook pointers. unsigned int last = config[0x7C]; unsigned int first = config[0x7D]; @@ -412,16 +421,13 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v return DC_STATUS_DATAFORMAT; } - // Memory buffer for the profile data. - unsigned char buffer[RB_PROFILE_END - RB_PROFILE_BEGIN] = {0}; - - unsigned int available = 0; - unsigned int offset = RB_PROFILE_END - RB_PROFILE_BEGIN; - - unsigned int previous = eop; - unsigned int address = previous; - + // The logbook ringbuffer can store at most 60 dives, even if the profile + // data could store more (e.g. many small dives). But it's also possible + // that the profile ringbuffer is filled faster than the logbook ringbuffer + // (e.g. many large dives). We detect this by checking the total length. + unsigned int total = 0; unsigned int idx = last; + unsigned int previous = eop; for (unsigned int i = 0; i < count; ++i) { // Get the pointer to the profile data. unsigned int current = array_uint16_le (config + 2 * idx) * SZ_PAGE + BASE; @@ -438,6 +444,68 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v // Get the profile length. unsigned int length = ringbuffer_distance (current, previous, 1, RB_PROFILE_BEGIN, RB_PROFILE_END); + // Check for a ringbuffer overflow. + if (total + length > RB_PROFILE_END - RB_PROFILE_BEGIN) { + count = i; + break; + } + + total += length; + + previous = current; + + if (idx == RB_LOGBOOK_BEGIN) + idx = RB_LOGBOOK_END; + idx--; + } + + // Because dives are not necessary aligned to packet boundaries, and + // we always do aligned reads, there can be padding bytes present on + // both sides of the memory buffer. These extra bytes need to be + // included in the total length. + total += (previous - ifloor(previous, SZ_PACKET)) + + (iceil(eop, SZ_PACKET) - eop); + + // Update and emit a progress event. + progress.current += SZ_PACKET; + progress.maximum = SZ_PACKET + total; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + // Memory buffer for the profile data. + unsigned char *buffer = (unsigned char *) malloc (total); + if (buffer == NULL) { + ERROR (abstract->context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + unsigned int available = 0; + unsigned int offset = total; + + // Align the initial memory address to the next packet boundary, and + // calculate the amount of padding bytes, so we can easily skip + // them later. + unsigned int address = iceil(eop, SZ_PACKET); + unsigned int skip = address - eop; + + idx = last; + previous = eop; + for (unsigned int i = 0; i < count; ++i) { + // Get the pointer to the profile data. + unsigned int current = array_uint16_le (config + 2 * idx) * SZ_PAGE + BASE; + if (current < RB_PROFILE_BEGIN || current >= RB_PROFILE_END) { + ERROR (abstract->context, "Invalid ringbuffer pointer detected."); + free(buffer); + return DC_STATUS_DATAFORMAT; + } + + // Position the pointer at the start of the header. + if (current == RB_PROFILE_BEGIN) + current = RB_PROFILE_END; + current -= SZ_PAGE; + + // Get the profile length. + unsigned int length = ringbuffer_distance (current, previous, 1, RB_PROFILE_BEGIN, RB_PROFILE_END); + unsigned nbytes = available; while (nbytes < length) { if (address == RB_PROFILE_BEGIN) @@ -449,6 +517,7 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v rc = cressi_edy_device_read (abstract, address, buffer + offset, SZ_PACKET); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the memory page."); + free(buffer); return rc; } @@ -456,7 +525,8 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v progress.current += SZ_PACKET; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - nbytes += SZ_PACKET; + nbytes += SZ_PACKET - skip; + skip = 0; } available = nbytes - length; @@ -465,15 +535,17 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v unsigned char *p = buffer + offset + available; if (memcmp (p, device->fingerprint, sizeof (device->fingerprint)) == 0) - return DC_STATUS_SUCCESS; + break; if (callback && !callback (p, length, p, sizeof (device->fingerprint), userdata)) - return DC_STATUS_SUCCESS; + break; if (idx == RB_LOGBOOK_BEGIN) idx = RB_LOGBOOK_END; idx--; } + free(buffer); + return DC_STATUS_SUCCESS; } From 9ae985a4990f51ca0d54571736e6723fa10c5977 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 21 Mar 2015 10:29:40 +0100 Subject: [PATCH 5/9] Fix the ringbuffer start address. Currently the profile ringbuffer starts at the base address 0x4000, but I believe the real start is one 0x20 byte page earlier, at 0x3FE0. I have two reasons for this: 1. To locate the start of a dive, we always have to substract one page from the pointers in the logbook ringbuffer. With the new base address, they would point directly to the start of the dive, which makes a lot more sense. 2. When comparing the divetime as stored in the header with the one obtained by counting the number of samples, they always match except for dives that span the ringbuffer wrap point. If those extra 0x20 bytes are included, the counts do match again. Unfortunately, this change breaks the assumption that the ringbuffer is aligned to packet boundaries. As a workaround, we define a virtual ringbuffer that is slightly larger than the actual ringbuffer, but properly aligned. Data outside the real ringbuffer is downloaded and then immediately dropped. --- src/cressi_edy.c | 49 +++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/cressi_edy.c b/src/cressi_edy.c index 4b58743..c45548c 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -47,7 +47,7 @@ #define BASE 0x4000 -#define RB_PROFILE_BEGIN 0x4000 +#define RB_PROFILE_BEGIN 0x3FE0 #define RB_PROFILE_END 0x7F80 #define RB_LOGBOOK_OFFSET 0x7F80 @@ -436,11 +436,6 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v return DC_STATUS_DATAFORMAT; } - // Position the pointer at the start of the header. - if (current == RB_PROFILE_BEGIN) - current = RB_PROFILE_END; - current -= SZ_PAGE; - // Get the profile length. unsigned int length = ringbuffer_distance (current, previous, 1, RB_PROFILE_BEGIN, RB_PROFILE_END); @@ -481,6 +476,13 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v unsigned int available = 0; unsigned int offset = total; + // Align the ringbuffer to packet boundaries. This results in a + // virtual ringbuffer that is slightly larger than the actual + // ringbuffer. Data outside the real ringbuffer is downloaded + // and then immediately dropped. + unsigned int rb_profile_begin = ifloor(RB_PROFILE_BEGIN, SZ_PACKET); + unsigned int rb_profile_end = iceil(RB_PROFILE_END, SZ_PACKET); + // Align the initial memory address to the next packet boundary, and // calculate the amount of padding bytes, so we can easily skip // them later. @@ -498,34 +500,47 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v return DC_STATUS_DATAFORMAT; } - // Position the pointer at the start of the header. - if (current == RB_PROFILE_BEGIN) - current = RB_PROFILE_END; - current -= SZ_PAGE; - // Get the profile length. unsigned int length = ringbuffer_distance (current, previous, 1, RB_PROFILE_BEGIN, RB_PROFILE_END); unsigned nbytes = available; while (nbytes < length) { - if (address == RB_PROFILE_BEGIN) - address = RB_PROFILE_END; + if (address == rb_profile_begin) + address = rb_profile_end; address -= SZ_PACKET; - offset -= SZ_PACKET; // Read the memory page. - rc = cressi_edy_device_read (abstract, address, buffer + offset, SZ_PACKET); + unsigned char packet[SZ_PACKET]; + rc = cressi_edy_device_read (abstract, address, packet, sizeof(packet)); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the memory page."); free(buffer); return rc; } + // At the head and tail of the ringbuffer, the packet can + // contain extra data, originating from the larger virtual + // ringbuffer. This data must be removed from the packet. + unsigned int head = 0; + unsigned int tail = 0; + unsigned int len = SZ_PACKET; + if (address < RB_PROFILE_BEGIN) { + head = RB_PROFILE_BEGIN - address; + } + if (address + SZ_PACKET > RB_PROFILE_END) { + tail = (address + SZ_PACKET) - RB_PROFILE_END; + } + len -= head + tail; + offset -= len; + + // Copy the data packet to the buffer. + memcpy(buffer + offset, packet + head, len); + // Update and emit a progress event. - progress.current += SZ_PACKET; + progress.current += len; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); - nbytes += SZ_PACKET - skip; + nbytes += len - skip; skip = 0; } From f0974ed6bcc8922fed6aabeb75e69128e50f00a1 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sun, 22 Mar 2015 22:26:39 +0100 Subject: [PATCH 6/9] Replace hardcoded constants with a layout descriptor. --- src/cressi_edy.c | 93 ++++++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/src/cressi_edy.c b/src/cressi_edy.c index c45548c..49cefd9 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -41,22 +41,23 @@ #define MAXRETRIES 4 -#define SZ_MEMORY 0x8000 #define SZ_PACKET 0x80 #define SZ_PAGE (SZ_PACKET / 4) -#define BASE 0x4000 - -#define RB_PROFILE_BEGIN 0x3FE0 -#define RB_PROFILE_END 0x7F80 - -#define RB_LOGBOOK_OFFSET 0x7F80 -#define RB_LOGBOOK_BEGIN 0 -#define RB_LOGBOOK_END 60 +typedef struct cressi_edy_layout_t { + unsigned int memsize; + unsigned int rb_profile_begin; + unsigned int rb_profile_end; + unsigned int rb_logbook_offset; + unsigned int rb_logbook_begin; + unsigned int rb_logbook_end; + unsigned int config; +} cressi_edy_layout_t; typedef struct cressi_edy_device_t { dc_device_t base; serial_t *port; + const cressi_edy_layout_t *layout; unsigned char fingerprint[SZ_PAGE / 2]; unsigned int model; } cressi_edy_device_t; @@ -77,6 +78,16 @@ static const dc_device_vtable_t cressi_edy_device_vtable = { cressi_edy_device_close /* close */ }; +static const cressi_edy_layout_t cressi_edy_layout = { + 0x8000, /* memsize */ + 0x3FE0, /* rb_profile_begin */ + 0x7F80, /* rb_profile_end */ + 0x7F80, /* rb_logbook_offset */ + 0, /* rb_logbook_begin */ + 60, /* rb_logbook_end */ + 0x7C, /* config */ +}; + static unsigned int ifloor (unsigned int x, unsigned int n) { @@ -224,6 +235,7 @@ cressi_edy_device_open (dc_device_t **out, dc_context_t *context, const char *na // Set the default values. device->port = NULL; + device->layout = &cressi_edy_layout; device->model = 0; memset (device->fingerprint, 0, sizeof (device->fingerprint)); @@ -362,9 +374,11 @@ cressi_edy_device_set_fingerprint (dc_device_t *abstract, const unsigned char da static dc_status_t cressi_edy_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) { + cressi_edy_device_t *device = (cressi_edy_device_t *) abstract; + // Erase the current contents of the buffer and // allocate the required amount of memory. - if (!dc_buffer_clear (buffer) || !dc_buffer_resize (buffer, SZ_MEMORY)) { + if (!dc_buffer_clear (buffer) || !dc_buffer_resize (buffer, device->layout->memsize)) { ERROR (abstract->context, "Insufficient buffer space available."); return DC_STATUS_NOMEMORY; } @@ -378,11 +392,12 @@ static dc_status_t cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) { cressi_edy_device_t *device = (cressi_edy_device_t *) abstract; + const cressi_edy_layout_t *layout = device->layout; // Enable progress notifications. dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; progress.maximum = SZ_PACKET + - (RB_PROFILE_END - RB_PROFILE_BEGIN); + (layout->rb_profile_end - layout->rb_profile_begin); device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Emit a device info event. @@ -392,19 +407,19 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v devinfo.serial = 0; device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); - // Read the configuration data. - unsigned char config[SZ_PACKET] = {0}; - dc_status_t rc = cressi_edy_device_read (abstract, 0x7F80, config, sizeof (config)); + // Read the logbook data. + unsigned char logbook[SZ_PACKET] = {0}; + dc_status_t rc = cressi_edy_device_read (abstract, layout->rb_logbook_offset, logbook, sizeof (logbook)); if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the configuration data."); + ERROR (abstract->context, "Failed to read the logbook data."); return rc; } // Get the logbook pointers. - unsigned int last = config[0x7C]; - unsigned int first = config[0x7D]; - if (first < RB_LOGBOOK_BEGIN || first >= RB_LOGBOOK_END || - last < RB_LOGBOOK_BEGIN || last >= RB_LOGBOOK_END) { + unsigned int last = logbook[layout->config + 0]; + unsigned int first = logbook[layout->config + 1]; + if (first < layout->rb_logbook_begin || first >= layout->rb_logbook_end || + last < layout->rb_logbook_begin || last >= layout->rb_logbook_end) { if (last == 0xFF) return DC_STATUS_SUCCESS; ERROR (abstract->context, "Invalid ringbuffer pointer detected."); @@ -412,11 +427,11 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v } // Get the number of logbook items. - unsigned int count = ringbuffer_distance (first, last, 0, RB_LOGBOOK_BEGIN, RB_LOGBOOK_END) + 1; + unsigned int count = ringbuffer_distance (first, last, 0, layout->rb_logbook_begin, layout->rb_logbook_end) + 1; // Get the profile pointer. - unsigned int eop = array_uint16_le (config + 0x7E) * SZ_PAGE + BASE; - if (eop < RB_PROFILE_BEGIN || eop >= RB_PROFILE_END) { + unsigned int eop = array_uint16_le (logbook + layout->config + 2) * SZ_PAGE + layout->rb_profile_begin; + if (eop < layout->rb_profile_begin || eop >= layout->rb_profile_end) { ERROR (abstract->context, "Invalid ringbuffer pointer detected."); return DC_STATUS_DATAFORMAT; } @@ -430,17 +445,17 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v unsigned int previous = eop; for (unsigned int i = 0; i < count; ++i) { // Get the pointer to the profile data. - unsigned int current = array_uint16_le (config + 2 * idx) * SZ_PAGE + BASE; - if (current < RB_PROFILE_BEGIN || current >= RB_PROFILE_END) { + unsigned int current = array_uint16_le (logbook + 2 * idx) * SZ_PAGE + layout->rb_profile_begin; + if (current < layout->rb_profile_begin || current >= layout->rb_profile_end) { ERROR (abstract->context, "Invalid ringbuffer pointer detected."); return DC_STATUS_DATAFORMAT; } // Get the profile length. - unsigned int length = ringbuffer_distance (current, previous, 1, RB_PROFILE_BEGIN, RB_PROFILE_END); + unsigned int length = ringbuffer_distance (current, previous, 1, layout->rb_profile_begin, layout->rb_profile_end); // Check for a ringbuffer overflow. - if (total + length > RB_PROFILE_END - RB_PROFILE_BEGIN) { + if (total + length > layout->rb_profile_end - layout->rb_profile_begin) { count = i; break; } @@ -449,8 +464,8 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v previous = current; - if (idx == RB_LOGBOOK_BEGIN) - idx = RB_LOGBOOK_END; + if (idx == layout->rb_logbook_begin) + idx = layout->rb_logbook_end; idx--; } @@ -480,8 +495,8 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v // virtual ringbuffer that is slightly larger than the actual // ringbuffer. Data outside the real ringbuffer is downloaded // and then immediately dropped. - unsigned int rb_profile_begin = ifloor(RB_PROFILE_BEGIN, SZ_PACKET); - unsigned int rb_profile_end = iceil(RB_PROFILE_END, SZ_PACKET); + unsigned int rb_profile_begin = ifloor(layout->rb_profile_begin, SZ_PACKET); + unsigned int rb_profile_end = iceil(layout->rb_profile_end, SZ_PACKET); // Align the initial memory address to the next packet boundary, and // calculate the amount of padding bytes, so we can easily skip @@ -493,15 +508,15 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v previous = eop; for (unsigned int i = 0; i < count; ++i) { // Get the pointer to the profile data. - unsigned int current = array_uint16_le (config + 2 * idx) * SZ_PAGE + BASE; - if (current < RB_PROFILE_BEGIN || current >= RB_PROFILE_END) { + unsigned int current = array_uint16_le (logbook + 2 * idx) * SZ_PAGE + layout->rb_profile_begin; + if (current < layout->rb_profile_begin || current >= layout->rb_profile_end) { ERROR (abstract->context, "Invalid ringbuffer pointer detected."); free(buffer); return DC_STATUS_DATAFORMAT; } // Get the profile length. - unsigned int length = ringbuffer_distance (current, previous, 1, RB_PROFILE_BEGIN, RB_PROFILE_END); + unsigned int length = ringbuffer_distance (current, previous, 1, layout->rb_profile_begin, layout->rb_profile_end); unsigned nbytes = available; while (nbytes < length) { @@ -524,11 +539,11 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v unsigned int head = 0; unsigned int tail = 0; unsigned int len = SZ_PACKET; - if (address < RB_PROFILE_BEGIN) { - head = RB_PROFILE_BEGIN - address; + if (address < layout->rb_profile_begin) { + head = layout->rb_profile_begin - address; } - if (address + SZ_PACKET > RB_PROFILE_END) { - tail = (address + SZ_PACKET) - RB_PROFILE_END; + if (address + SZ_PACKET > layout->rb_profile_end) { + tail = (address + SZ_PACKET) - layout->rb_profile_end; } len -= head + tail; offset -= len; @@ -555,8 +570,8 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v if (callback && !callback (p, length, p, sizeof (device->fingerprint), userdata)) break; - if (idx == RB_LOGBOOK_BEGIN) - idx = RB_LOGBOOK_END; + if (idx == layout->rb_logbook_begin) + idx = layout->rb_logbook_end; idx--; } From 311bc856454391a8a02d2d44aa9141e41e800108 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 14 Apr 2015 22:48:48 +0200 Subject: [PATCH 7/9] Add support for the Tusa IQ-700. The Tusa IQ-700 is very similar to the other Seiko based models. The most important change is that due the smaller amount of memory (8K vs 32K), the logbook entries are only 1 byte large instead of two bytes. --- src/array.c | 23 +++++++++++++++++++++++ src/array.h | 6 ++++++ src/cressi_edy.c | 30 ++++++++++++++++++++++++++---- src/cressi_edy_parser.c | 15 ++++++++++----- src/descriptor.c | 3 ++- 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/array.c b/src/array.c index be8d696..26a6c1b 100644 --- a/src/array.c +++ b/src/array.c @@ -146,6 +146,29 @@ array_convert_hex2bin (const unsigned char input[], unsigned int isize, unsigned return 0; } +unsigned int +array_uint_be (const unsigned char data[], unsigned int n) +{ + unsigned int shift = n * 8; + unsigned int value = 0; + for (unsigned int i = 0; i < n; ++i) { + shift -= 8; + value |= data[i] << shift; + } + return value; +} + +unsigned int +array_uint_le (const unsigned char data[], unsigned int n) +{ + unsigned int shift = 0; + unsigned int value = 0; + for (unsigned int i = 0; i < n; ++i) { + value |= data[i] << shift; + shift += 8; + } + return value; +} unsigned int array_uint32_be (const unsigned char data[]) diff --git a/src/array.h b/src/array.h index 622e0d5..49e8ab1 100644 --- a/src/array.h +++ b/src/array.h @@ -49,6 +49,12 @@ array_convert_bin2hex (const unsigned char input[], unsigned int isize, unsigned int array_convert_hex2bin (const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize); +unsigned int +array_uint_be (const unsigned char data[], unsigned int n); + +unsigned int +array_uint_le (const unsigned char data[], unsigned int n); + unsigned int array_uint32_be (const unsigned char data[]); diff --git a/src/cressi_edy.c b/src/cressi_edy.c index 49cefd9..504b79c 100644 --- a/src/cressi_edy.c +++ b/src/cressi_edy.c @@ -44,11 +44,15 @@ #define SZ_PACKET 0x80 #define SZ_PAGE (SZ_PACKET / 4) +#define IQ700 0x05 +#define EDY 0x08 + typedef struct cressi_edy_layout_t { unsigned int memsize; unsigned int rb_profile_begin; unsigned int rb_profile_end; unsigned int rb_logbook_offset; + unsigned int rb_logbook_size; unsigned int rb_logbook_begin; unsigned int rb_logbook_end; unsigned int config; @@ -83,11 +87,23 @@ static const cressi_edy_layout_t cressi_edy_layout = { 0x3FE0, /* rb_profile_begin */ 0x7F80, /* rb_profile_end */ 0x7F80, /* rb_logbook_offset */ + 2, /* rb_logbook_size */ 0, /* rb_logbook_begin */ 60, /* rb_logbook_end */ 0x7C, /* config */ }; +static const cressi_edy_layout_t tusa_iq700_layout = { + 0x2000, /* memsize */ + 0x0000, /* rb_profile_begin */ + 0x1F60, /* rb_profile_end */ + 0x1F80, /* rb_logbook_offset */ + 1, /* rb_logbook_size */ + 0, /* rb_logbook_begin */ + 60, /* rb_logbook_end */ + 0x3C, /* config */ +}; + static unsigned int ifloor (unsigned int x, unsigned int n) { @@ -235,7 +251,7 @@ cressi_edy_device_open (dc_device_t **out, dc_context_t *context, const char *na // Set the default values. device->port = NULL; - device->layout = &cressi_edy_layout; + device->layout = NULL; device->model = 0; memset (device->fingerprint, 0, sizeof (device->fingerprint)); @@ -282,6 +298,12 @@ cressi_edy_device_open (dc_device_t **out, dc_context_t *context, const char *na cressi_edy_init2 (device); cressi_edy_init3 (device); + if (device->model == IQ700) { + device->layout = &tusa_iq700_layout; + } else { + device->layout = &cressi_edy_layout; + } + // Set the serial communication protocol (4800 8N1). rc = serial_configure (device->port, 4800, 8, SERIAL_PARITY_NONE, 1, SERIAL_FLOWCONTROL_NONE); if (rc == -1) { @@ -430,7 +452,7 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v unsigned int count = ringbuffer_distance (first, last, 0, layout->rb_logbook_begin, layout->rb_logbook_end) + 1; // Get the profile pointer. - unsigned int eop = array_uint16_le (logbook + layout->config + 2) * SZ_PAGE + layout->rb_profile_begin; + unsigned int eop = array_uint_le (logbook + layout->config + 2, layout->rb_logbook_size) * SZ_PAGE + layout->rb_profile_begin; if (eop < layout->rb_profile_begin || eop >= layout->rb_profile_end) { ERROR (abstract->context, "Invalid ringbuffer pointer detected."); return DC_STATUS_DATAFORMAT; @@ -445,7 +467,7 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v unsigned int previous = eop; for (unsigned int i = 0; i < count; ++i) { // Get the pointer to the profile data. - unsigned int current = array_uint16_le (logbook + 2 * idx) * SZ_PAGE + layout->rb_profile_begin; + unsigned int current = array_uint_le (logbook + idx * layout->rb_logbook_size, layout->rb_logbook_size) * SZ_PAGE + layout->rb_profile_begin; if (current < layout->rb_profile_begin || current >= layout->rb_profile_end) { ERROR (abstract->context, "Invalid ringbuffer pointer detected."); return DC_STATUS_DATAFORMAT; @@ -508,7 +530,7 @@ cressi_edy_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, v previous = eop; for (unsigned int i = 0; i < count; ++i) { // Get the pointer to the profile data. - unsigned int current = array_uint16_le (logbook + 2 * idx) * SZ_PAGE + layout->rb_profile_begin; + unsigned int current = array_uint_le (logbook + idx * layout->rb_logbook_size, layout->rb_logbook_size) * SZ_PAGE + layout->rb_profile_begin; if (current < layout->rb_profile_begin || current >= layout->rb_profile_end) { ERROR (abstract->context, "Invalid ringbuffer pointer detected."); free(buffer); diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index e6d12a8..0c4d143 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -29,6 +29,9 @@ #define ISINSTANCE(parser) dc_parser_isinstance((parser), &cressi_edy_parser_vtable) +#define IQ700 0x05 +#define EDY 0x08 + typedef struct cressi_edy_parser_t cressi_edy_parser_t; struct cressi_edy_parser_t { @@ -145,7 +148,7 @@ cressi_edy_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign if (value) { switch (type) { case DC_FIELD_DIVETIME: - if (parser->model == 0x08) + if (parser->model == EDY) *((unsigned int *) value) = bcd2dec (p[0x0C] & 0x0F) * 60 + bcd2dec (p[0x0D]); else *((unsigned int *) value) = (bcd2dec (p[0x0C] & 0x0F) * 100 + bcd2dec (p[0x0D])) * 60; @@ -179,11 +182,13 @@ cressi_edy_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c unsigned int size = abstract->size; unsigned int time = 0; - unsigned int interval = 0; - if (parser->model == 0x08) + unsigned int interval = 30; + if (parser->model == EDY) { interval = 1; - else - interval = 30; + } else if (parser->model == IQ700) { + if (data[0x07] & 0x40) + interval = 15; + } unsigned int ngasmixes = cressi_edy_parser_count_gasmixes(data); unsigned int gasmix = 0xFFFFFFFF; diff --git a/src/descriptor.c b/src/descriptor.c index bfee247..879015d 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -217,7 +217,8 @@ static const dc_descriptor_t g_descriptors[] = { {"Heinrichs Weikamp", "OSTC cR", DC_FAMILY_HW_OSTC3, 0x05}, {"Heinrichs Weikamp", "OSTC Sport", DC_FAMILY_HW_OSTC3, 0x12}, /* Cressi Edy */ - {"Cressi", "Edy", DC_FAMILY_CRESSI_EDY, 0}, + {"Tusa", "IQ-700", DC_FAMILY_CRESSI_EDY, 0x05}, + {"Cressi", "Edy", DC_FAMILY_CRESSI_EDY, 0x08}, /* Cressi Leonardo */ {"Cressi", "Leonardo", DC_FAMILY_CRESSI_LEONARDO, 1}, {"Cressi", "Giotto", DC_FAMILY_CRESSI_LEONARDO, 4}, From 47874037ea6b5242db62e8bd8393083d90c74594 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 18 Apr 2015 20:17:52 +0200 Subject: [PATCH 8/9] Disable gas change events except for the intial mix. For the IQ-700, the existing code for the active gas mix always results in an out-of-range gas mix index. The index of the active gas mix is probably stored in another bit. As a temporary workaround, we simply assume the active gas mix is always the first gas mix. This should already produce correct results for dives with only single gas mix. --- src/cressi_edy_parser.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index 0c4d143..e6661d1 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -217,8 +217,12 @@ cressi_edy_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t c // Current gasmix if (ngasmixes) { unsigned int idx = (data[offset + 0] & 0x60) >> 5; - if (idx >= ngasmixes) + if (parser->model == IQ700) + idx = 0; /* FIXME */ + if (idx >= ngasmixes) { + ERROR (abstract->context, "Invalid gas mix index."); return DC_STATUS_DATAFORMAT; + } if (idx != gasmix) { sample.event.type = SAMPLE_EVENT_GASCHANGE; sample.event.time = 0; From d7c89bbc39acc378987570bffbc505bdc46a375c Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Sat, 18 Apr 2015 21:39:12 +0200 Subject: [PATCH 9/9] Add support for the temperature field. --- src/cressi_edy_parser.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cressi_edy_parser.c b/src/cressi_edy_parser.c index e6661d1..195eb41 100644 --- a/src/cressi_edy_parser.c +++ b/src/cressi_edy_parser.c @@ -164,6 +164,9 @@ cressi_edy_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsign gasmix->oxygen = bcd2dec (p[0x17 - flags]) / 100.0; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; + case DC_FIELD_TEMPERATURE_MINIMUM: + *((double *) value) = (bcd2dec (p[0x0B]) * 100 + bcd2dec (p[0x0C])) / 100.0; + break; default: return DC_STATUS_UNSUPPORTED; }