diff --git a/examples/suunto_vyper2_test.c b/examples/suunto_vyper2_test.c index 97de3a3..0b53e6f 100644 --- a/examples/suunto_vyper2_test.c +++ b/examples/suunto_vyper2_test.c @@ -1,6 +1,4 @@ #include // fopen, fwrite, fclose -#include // assert -#include // memcpy, memmove #include "suunto.h" #include "utils.h" @@ -10,21 +8,8 @@ message ("%s:%d: %s\n", __FILE__, __LINE__, expr); \ } -#define DISTANCE(a,b) distance (a, b, SUUNTO_VYPER2_MEMORY_SIZE - 0x019A) - -unsigned int -distance (unsigned int a, unsigned int b, unsigned int size) +int test_dump_sdm (const char* name) { - if (a <= b) { - return (b - a) % size; - } else { - return size - (a - b) % size; - } -} - -int test_dump_sdm (const char* name, const char* filename) -{ - unsigned char data[SUUNTO_VYPER2_MEMORY_SIZE] = {0}; vyper2 *device = NULL; message ("suunto_vyper2_open\n"); @@ -43,181 +28,13 @@ int test_dump_sdm (const char* name, const char* filename) return rc; } - message ("suunto_vyper2_read_memory\n"); - rc = suunto_vyper2_read_memory (device, 0x00, data + 0x00, SUUNTO_VYPER2_PACKET_SIZE); + message ("suunto_vyper2_read_dives\n"); + rc = suunto_vyper2_read_dives (device, NULL, NULL); if (rc != SUUNTO_SUCCESS) { - WARNING ("Cannot read memory."); + WARNING ("Cannot read dives."); suunto_vyper2_close (device); return rc; } - rc = suunto_vyper2_read_memory (device, 0x168, data + 0x168, SUUNTO_VYPER2_PACKET_SIZE); - if (rc != SUUNTO_SUCCESS) { - WARNING ("Cannot read memory."); - suunto_vyper2_close (device); - return rc; - } - rc = suunto_vyper2_read_memory (device, 0xF0, data + 0xF0, SUUNTO_VYPER2_PACKET_SIZE); - if (rc != SUUNTO_SUCCESS) { - WARNING ("Cannot read memory."); - suunto_vyper2_close (device); - return rc; - } - - message ("suunto_vyper2_read_dive\n"); - unsigned int last = data[0x0190] + (data[0x0191] << 8); - unsigned int count = data[0x0192] + (data[0x0193] << 8); - unsigned int end = data[0x0194] + (data[0x0195] << 8); - unsigned int begin = data[0x0196] + (data[0x0197] << 8); - message ("Pointers: begin=%04x, last=%04x, end=%04x, count=%i\n", begin, last, end, count); - - unsigned int index = 0x019A; - - // Calculate the total amount of bytes. - - unsigned int remaining = DISTANCE (begin, end); - - // To reduce the number of read operations, we always try to read - // packages with the largest possible size. As a consequence, the - // last package of a dive can contain data from more than one dive. - // Therefore, the remaining data of this package (and its size) - // needs to be preserved for the next dive. - // The package buffer also needs some extra space to store the - // pointer bytes (see later on for the reason). - - unsigned int available = 0; - unsigned char package[SUUNTO_VYPER2_PACKET_SIZE + 3] = {0}; - - // The ring buffer is traversed backwards to retrieve the most recent - // dives first. This allows you to download only the new dives. During - // the traversal, the current pointer does always point to the end of - // the dive data and we move to the "next" dive by means of the previous - // pointer. - - unsigned int ndives = 0; - unsigned int current = end; - unsigned int previous = last; - while (current != begin) { - // Calculate the size of the current dive. - unsigned int size = DISTANCE (previous, current); - message ("Pointers: dive=%u, current=%04x, previous=%04x, size=%u, remaining=%u\n", ndives + 1, current, previous, size, remaining); - assert (size >= 4); - - // Check if the output buffer is large enough to store the entire - // dive. The output buffer does not need space for the previous and - // next pointers (4 bytes). - unsigned char *pointer = data + index + size - 4; - if (sizeof (data) - index < size - 4) { - WARNING ("Insufficient buffer space available."); - return SUUNTO_ERROR_MEMORY; - } - - // If there is already some data available from downloading - // the previous dive, it is processed here. - if (available) { - // Calculate the offset to the package data. - unsigned int offset = 0; - if (available > size - 4) - offset = available - (size - 4); - - // Prepend the package data to the output buffer. - pointer -= available - offset; - memcpy (pointer, package + offset, available - offset); - } - - unsigned int nbytes = available; - unsigned int address = current - available; - while (nbytes < size) { - // Calculate the package size. Try with the largest possible - // size first, and adjust when the end of the ringbuffer or - // the end of the profile data is reached. - unsigned int len = SUUNTO_VYPER2_PACKET_SIZE; - if (0x019A + len > address) - len = address - 0x019A; // End of ringbuffer. - if (nbytes + len > remaining) - len = remaining - nbytes; // End of profile. - /*if (nbytes + len > size) - len = size - nbytes;*/ // End of dive (for testing only). - - // Calculate the offset to the package data, skipping the - // previous and next pointers (4 bytes) and also the data - // from the next dive (if present). Thus, the offset is only - // non-zero for packages at the end of the dive. - unsigned int offset = 0; - if (nbytes + len > size - 4) - offset = nbytes + len - (size - 4); - - message ("Pointers: address=%04x, len=%u, offset=%u\n", address - len, len, offset); - - // The previous and next pointers can be split over multiple - // packages. If that is the case, we move those byte(s) from - // the start of the previous package to the end of the current - // package. With this approach, the pointers are preserved when - // reading the current package (in the next step) and they are - // again in a continuous memory area. - if (offset > len) { - message ("Pointers: dst=%u, src=%u, len=%u\n", len, 0, offset - len); - memmove (package + len, package, offset - len); - } - - // Read the package. - rc = suunto_vyper2_read_memory (device, address - len, package, len); - if (rc != SUUNTO_SUCCESS) { - WARNING ("Cannot read memory."); - suunto_vyper2_close (device); - return rc; - } - - // Prepend the package data to the output buffer. - if (offset < len) { - pointer -= len - offset; - memcpy (pointer, package + offset, len - offset); - } - - // Next package. - nbytes += len; - address -= len; - if (address <= 0x019A) - address = SUUNTO_VYPER2_MEMORY_SIZE; - } - - // The last package of the current dive contains the previous and - // next pointers (in a continuous memory area). It can also contain - // a number of bytes from the next dive. The offset to the pointers - // is equal to the number of remaining (or "available") bytes. - - available = nbytes - size; - message ("Pointers: nbytes=%u, available=%u\n", nbytes, available); - - unsigned int oprevious = package[available + 0x00] + (package[available + 0x01] << 8); - unsigned int onext = package[available + 0x02] + (package[available + 0x03] << 8); - message ("Pointers: previous=%04x, next=%04x\n", oprevious, onext); - assert (current == onext); - - // Next dive. - current = previous; - previous = oprevious; - remaining -= size; - ndives++; - -#ifndef NDEBUG - message ("Vyper2Profile()=\""); - for (unsigned int i = 0; i < size - 4; ++i) { - message ("%02x", data[i + index]); - } - message ("\"\n"); -#endif - - index += size - 4; - } - assert (remaining == 0); - assert (available == 0); - - message ("Dumping data\n"); - FILE *fp = fopen (filename, "wb"); - if (fp != NULL) { - fwrite (data, sizeof (unsigned char), sizeof (data), fp); - fclose (fp); - } message ("suunto_vyper2_close\n"); rc = suunto_vyper2_close (device); @@ -312,7 +129,7 @@ int main(int argc, char *argv[]) message ("DEVICE=%s\n", name); int a = test_dump_memory (name, "VYPER2.DMP"); - int b = test_dump_sdm (name, "VYPER2.SDM"); + int b = test_dump_sdm (name); message ("\nSUMMARY\n"); message ("-------\n"); diff --git a/src/suunto_vyper2.c b/src/suunto_vyper2.c index a7a381c..4047d39 100644 --- a/src/suunto_vyper2.c +++ b/src/suunto_vyper2.c @@ -1,11 +1,13 @@ #include // memcmp, memcpy #include // malloc, free -#include // assert +#include // assert #include "suunto.h" #include "serial.h" #include "utils.h" +#define MAXRETRIES 2 + #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #define MAX(a,b) (((a) > (b)) ? (a) : (b)) @@ -14,12 +16,24 @@ message ("%s:%d: %s\n", __FILE__, __LINE__, expr); \ } +#define DISTANCE(a,b) distance (a, b, SUUNTO_VYPER2_MEMORY_SIZE - 0x019A - 2) struct vyper2 { struct serial *port; }; +static unsigned int +distance (unsigned int a, unsigned int b, unsigned int size) +{ + if (a <= b) { + return (b - a) % size; + } else { + return size - (a - b) % size; + } +} + + int suunto_vyper2_open (vyper2 **out, const char* name) { @@ -138,39 +152,48 @@ suunto_vyper2_transfer (vyper2 *device, const unsigned char command[], unsigned { assert (asize >= size + 4); - // Send the command to the dive computer. - int rc = suunto_vyper2_send (device, command, csize); - if (rc != SUUNTO_SUCCESS) { - WARNING ("Failed to send the command."); - return rc; - } + // Occasionally, the dive computer does not respond to a command. + // In that case we retry the command a number of times before + // returning an error. Usually the dive computer will respond + // again during one of the retries. - // Receive the answer of the dive computer. - rc = serial_read (device->port, answer, asize); - if (rc != asize) { - WARNING ("Failed to receive the answer."); - if (rc == -1) - return SUUNTO_ERROR_IO; - return SUUNTO_ERROR_TIMEOUT; - } + for (unsigned int i = 0;; ++i) { + // Send the command to the dive computer. + int rc = suunto_vyper2_send (device, command, csize); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Failed to send the command."); + return rc; + } - // Verify the header of the package. - answer[2] -= size; // Adjust the package size for the comparision. - if (memcmp (command, answer, asize - size - 1) != 0) { - WARNING ("Unexpected answer start byte(s)."); - return SUUNTO_ERROR_PROTOCOL; - } - answer[2] += size; // Restore the package size again. + // Receive the answer of the dive computer. + rc = serial_read (device->port, answer, asize); + if (rc != asize) { + WARNING ("Failed to receive the answer."); + if (rc == -1) + return SUUNTO_ERROR_IO; + if (i < MAXRETRIES) + continue; // Retry. + return SUUNTO_ERROR_TIMEOUT; + } - // Verify the checksum of the package. - unsigned char crc = answer[asize - 1]; - unsigned char ccrc = suunto_vyper2_checksum (answer, asize - 1, 0x00); - if (crc != ccrc) { - WARNING ("Unexpected answer CRC."); - return SUUNTO_ERROR_PROTOCOL; - } + // Verify the header of the package. + answer[2] -= size; // Adjust the package size for the comparision. + if (memcmp (command, answer, asize - size - 1) != 0) { + WARNING ("Unexpected answer start byte(s)."); + return SUUNTO_ERROR_PROTOCOL; + } + answer[2] += size; // Restore the package size again. - return SUUNTO_SUCCESS; + // Verify the checksum of the package. + unsigned char crc = answer[asize - 1]; + unsigned char ccrc = suunto_vyper2_checksum (answer, asize - 1, 0x00); + if (crc != ccrc) { + WARNING ("Unexpected answer CRC."); + return SUUNTO_ERROR_PROTOCOL; + } + + return SUUNTO_SUCCESS; + } } @@ -306,3 +329,126 @@ suunto_vyper2_write_memory (vyper2 *device, unsigned int address, const unsigned return SUUNTO_SUCCESS; } + + +int +suunto_vyper2_read_dives (vyper2 *device, dive_callback_t callback, void *userdata) +{ + if (device == NULL) + return SUUNTO_ERROR; + + // Read the header bytes. + unsigned char header[8] = {0}; + int rc = suunto_vyper2_read_memory (device, 0x0190, header, sizeof (header)); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot read memory header."); + return rc; + } + + // Obtain the pointers from the header. + unsigned int last = header[0] + (header[1] << 8); + unsigned int count = header[2] + (header[3] << 8); + unsigned int end = header[4] + (header[5] << 8); + unsigned int begin = header[6] + (header[7] << 8); + message ("Pointers: begin=%04x, last=%04x, end=%04x, count=%i\n", begin, last, end, count); + + // Memory buffer to store all the dives. + + unsigned char data[SUUNTO_VYPER2_MEMORY_SIZE - 0x019A - 2] = {0}; + + // Calculate the total amount of bytes. + + unsigned int remaining = DISTANCE (begin, end); + + // To reduce the number of read operations, we always try to read + // packages with the largest possible size. As a consequence, the + // last package of a dive can contain data from more than one dive. + // Therefore, the remaining data of this package (and its size) + // needs to be preserved for the next dive. + + unsigned int available = 0; + + // The ring buffer is traversed backwards to retrieve the most recent + // dives first. This allows you to download only the new dives. During + // the traversal, the current pointer does always point to the end of + // the dive data and we move to the "next" dive by means of the previous + // pointer. + + unsigned int ndives = 0; + unsigned int current = end; + unsigned int previous = last; + while (current != begin) { + // Calculate the size of the current dive. + unsigned int size = DISTANCE (previous, current); + message ("Pointers: dive=%u, current=%04x, previous=%04x, size=%u, remaining=%u, available=%u\n", + ndives + 1, current, previous, size, remaining, available); + + assert (size >= 4 && size <= remaining); + + unsigned int nbytes = available; + unsigned int address = current - available; + while (nbytes < size) { + // Calculate the package size. Try with the largest possible + // size first, and adjust when the end of the ringbuffer or + // the end of the profile data is reached. + unsigned int len = SUUNTO_VYPER2_PACKET_SIZE; + if (0x019A + len > address) + len = address - 0x019A; // End of ringbuffer. + if (nbytes + len > remaining) + len = remaining - nbytes; // End of profile. + /*if (nbytes + len > size) + len = size - nbytes;*/ // End of dive (for testing only). + + message ("Pointers: address=%04x, len=%u\n", address - len, len); + + // Read the package. + unsigned char *p = data + remaining - nbytes; + rc = suunto_vyper2_read_memory (device, address - len, p - len, len); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot read memory."); + return rc; + } + + // Next package. + nbytes += len; + address -= len; + if (address <= 0x019A) + address = SUUNTO_VYPER2_MEMORY_SIZE - 2; + } + + message ("Pointers: nbytes=%u\n", nbytes); + + // The last package of the current dive contains the previous and + // next pointers (in a continuous memory area). It can also contain + // a number of bytes from the next dive. The offset to the pointers + // is equal to the number of bytes remaining after the current dive. + + remaining -= size; + available = nbytes - size; + + unsigned int oprevious = data[remaining + 0] + (data[remaining + 1] << 8); + unsigned int onext = data[remaining + 2] + (data[remaining + 3] << 8); + message ("Pointers: previous=%04x, next=%04x\n", oprevious, onext); + assert (current == onext); + + // Next dive. + current = previous; + previous = oprevious; + ndives++; + +#ifndef NDEBUG + message ("Vyper2Profile()=\""); + for (unsigned int i = 0; i < size - 4; ++i) { + message ("%02x", data[remaining + 4 + i]); + } + message ("\"\n"); +#endif + + if (callback) + callback (data + remaining + 4, size - 4, userdata); + } + assert (remaining == 0); + assert (available == 0); + + return SUUNTO_SUCCESS; +} diff --git a/src/suunto_vyper2.h b/src/suunto_vyper2.h index 2c89f7f..4adc7d3 100644 --- a/src/suunto_vyper2.h +++ b/src/suunto_vyper2.h @@ -22,6 +22,8 @@ int suunto_vyper2_read_memory (vyper2 *device, unsigned int address, unsigned ch int suunto_vyper2_write_memory (vyper2 *device, unsigned int address, const unsigned char data[], unsigned int size); +int suunto_vyper2_read_dives (vyper2 *device, dive_callback_t callback, void *userdata); + #ifdef __cplusplus } #endif /* __cplusplus */