diff --git a/suunto.h b/suunto.h index 76c0baf..05a9427 100644 --- a/suunto.h +++ b/suunto.h @@ -10,5 +10,6 @@ #include "suunto_vyper.h" #include "suunto_vyper2.h" +#include "suunto_d9.h" #endif /* SUUNTO_H */ diff --git a/suunto_d9.c b/suunto_d9.c new file mode 100644 index 0000000..45eba0d --- /dev/null +++ b/suunto_d9.c @@ -0,0 +1,327 @@ +#include // memcmp, memcpy +#include // malloc, free +#include // assert + +#include "suunto.h" +#include "serial.h" +#include "utils.h" + +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) + +#define WARNING(expr) \ +{ \ + message ("%s:%d: %s\n", __FILE__, __LINE__, expr); \ +} + +#define EXITCODE(rc, n) \ +( \ + rc == -1 ? \ + SUUNTO_ERROR_IO : \ + (rc != n ? SUUNTO_ERROR_TIMEOUT : SUUNTO_ERROR_PROTOCOL) \ +) + + +struct d9 { + struct serial *port; +}; + + +int +suunto_d9_open (d9 **out, const char* name) +{ + if (out == NULL) + return SUUNTO_ERROR; + + // Allocate memory. + struct d9 *device = malloc (sizeof (struct d9)); + if (device == NULL) { + WARNING ("Failed to allocate memory."); + return SUUNTO_ERROR_MEMORY; + } + + // Set the default values. + device->port = NULL; + + // Open the device. + int rc = serial_open (&device->port, name); + if (rc == -1) { + WARNING ("Failed to open the serial port."); + free (device); + return SUUNTO_ERROR_IO; + } + + // Set the serial communication protocol (9600 8N1). + rc = serial_configure (device->port, 9600, 8, SERIAL_PARITY_NONE, 1, SERIAL_FLOWCONTROL_NONE); + if (rc == -1) { + WARNING ("Failed to set the terminal attributes."); + serial_close (device->port); + free (device); + return SUUNTO_ERROR_IO; + } + + // Set the timeout for receiving data (3000 ms). + if (serial_set_timeout (device->port, 3000) == -1) { + WARNING ("Failed to set the timeout."); + serial_close (device->port); + free (device); + return SUUNTO_ERROR_IO; + } + + // Set the DTR line (power supply for the interface). + if (serial_set_dtr (device->port, 1) == -1) { + WARNING ("Failed to set the DTR line."); + serial_close (device->port); + free (device); + return SUUNTO_ERROR_IO; + } + + // Give the interface 100 ms to settle and draw power up. + serial_sleep (100); + + // Make sure everything is in a sane state. + serial_flush (device->port, SERIAL_QUEUE_BOTH); + + *out = device; + + return SUUNTO_SUCCESS; +} + + +int +suunto_d9_close (d9 *device) +{ + if (device == NULL) + return SUUNTO_SUCCESS; + + // Close the device. + if (serial_close (device->port) == -1) { + free (device); + return SUUNTO_ERROR_IO; + } + + // Free memory. + free (device); + + return SUUNTO_SUCCESS; +} + + +static unsigned char +suunto_d9_checksum (const unsigned char data[], unsigned int size, unsigned char init) +{ + unsigned char crc = init; + for (unsigned int i = 0; i < size; ++i) + crc ^= data[i]; + + return crc; +} + + +static int +suunto_d9_send (d9 *device, const unsigned char command[], unsigned int csize) +{ + // Clear RTS to send the command. + serial_set_rts (device->port, 0); + + // Send the command to the dive computer and + // wait until all data has been transmitted. + serial_write (device->port, command, csize); + serial_drain (device->port); + + // Receive the echo. + unsigned char echo[128] = {0}; + assert (sizeof (echo) >= csize); + int rc = serial_read (device->port, echo, csize); + if (rc != csize || memcmp (command, echo, csize) != 0) { + WARNING ("Unexpected echo."); + return EXITCODE (rc, csize); + } + + // Set RTS to receive the reply. + serial_set_rts (device->port, 1); + + return SUUNTO_SUCCESS; +} + + +static int +suunto_d9_transfer (d9 *device, const unsigned char command[], unsigned int csize, unsigned char header[], unsigned int hsize, unsigned char data[], unsigned int size) +{ + // Send the command to the dive computer. + int rc = suunto_d9_send (device, command, csize); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Failed to send the command."); + return rc; + } + + // Initial checksum value. + unsigned char ccrc = 0x00; + + // Receive the header of the package. + rc = serial_read (device->port, header, hsize); + header[2] -= size; + if (rc != hsize || memcmp (command, header, hsize) != 0) { + WARNING ("Unexpected answer start byte(s)."); + if (rc == 0) { + WARNING ("Interface present, but the DC does not answer. Check the connection."); + } + return EXITCODE (rc, hsize); + } + header[2] += size; + // Update the checksum. + ccrc = suunto_d9_checksum (header, hsize, ccrc); + + if (data) { + // Receive the contents of the package. + rc = serial_read (device->port, data, size); + if (rc != size) { + WARNING ("Unexpected EOF in answer."); + return EXITCODE (rc, size); + } + // Update the checksum. + ccrc = suunto_d9_checksum (data, size, ccrc); + } + + // Receive (and verify) the checksum of the package. + unsigned char crc = 0x00; + rc = serial_read (device->port, &crc, 1); + if (rc != 1 || ccrc != crc) { + WARNING ("Unexpected answer CRC."); + return EXITCODE (rc, 1); + } + + return SUUNTO_SUCCESS; +} + + +int +suunto_d9_read_version (d9 *device, unsigned char data[], unsigned int size) +{ + if (device == NULL) + return SUUNTO_ERROR; + + if (size < 4) + return SUUNTO_ERROR_MEMORY; + + unsigned char header[3] = {0}; + unsigned char command[4] = {0x0F, 0x00, 0x00, 0}; + command[3] = suunto_d9_checksum (command, 3, 0x00); + int rc = suunto_d9_transfer (device, command, 4, header, 3, data, 4); + if (rc != SUUNTO_SUCCESS) + return rc; + +#ifndef NDEBUG + message ("D9ReadVersion()=\"%02x %02x %02x %02x\"\n", data[0], data[1], data[2], data[3]); +#endif + + return SUUNTO_SUCCESS; +} + + +int +suunto_d9_reset_maxdepth (d9 *device) +{ + if (device == NULL) + return SUUNTO_ERROR; + + unsigned char header[3] = {0}; + unsigned char command[4] = {0x20, 0x00, 0x00, 0}; + command[3] = suunto_d9_checksum (command, 3, 0x00); + int rc = suunto_d9_transfer (device, command, 4, header, 3, NULL, 0); + if (rc != SUUNTO_SUCCESS) + return rc; + +#ifndef NDEBUG + message ("D9ResetMaxDepth()\n"); +#endif + + return SUUNTO_SUCCESS; +} + + +int +suunto_d9_read_memory (d9 *device, unsigned int address, unsigned char data[], unsigned int size) +{ + if (device == NULL) + return SUUNTO_ERROR; + + // The data transmission is split in packages + // of maximum $SUUNTO_D9_PACKET_SIZE bytes. + + unsigned int nbytes = 0; + while (nbytes < size) { + // Calculate the package size. + unsigned int len = MIN (size - nbytes, SUUNTO_D9_PACKET_SIZE); + + // Read the package. + unsigned char header[6] = {0}; + unsigned char command[7] = {0x05, 0x00, 0x03, + (address >> 8) & 0xFF, // high + (address ) & 0xFF, // low + len, // count + 0}; // CRC + command[6] = suunto_d9_checksum (command, 6, 0x00); + int rc = suunto_d9_transfer (device, command, 7, header, 6, data, len); + if (rc != SUUNTO_SUCCESS) + return rc; + +#ifndef NDEBUG + message ("D9Read(0x%04x,%d)=\"", address, len); + for (unsigned int i = 0; i < len; ++i) { + message("%02x", data[i]); + } + message("\"\n"); +#endif + + nbytes += len; + address += len; + data += len; + } + + return SUUNTO_SUCCESS; +} + + +int +suunto_d9_write_memory (d9 *device, unsigned int address, const unsigned char data[], unsigned int size) +{ + if (device == NULL) + return SUUNTO_ERROR; + + // The data transmission is split in packages + // of maximum $SUUNTO_D9_PACKET_SIZE bytes. + + unsigned int nbytes = 0; + while (nbytes < size) { + // Calculate the package size. + unsigned int len = MIN (size - nbytes, SUUNTO_D9_PACKET_SIZE); + + // Write the package. + unsigned char header[6] = {0}; + unsigned char command[SUUNTO_D9_PACKET_SIZE + 7] = {0x06, 0x00, 0x03, + (address >> 8) & 0xFF, // high + (address ) & 0xFF, // low + len, // count + 0}; // data + CRC + memcpy (command + 6, data, len); + command[len + 6] = suunto_d9_checksum (command, len + 6, 0x00); + int rc = suunto_d9_transfer (device, command, len + 7, header, 6, NULL, 0); + if (rc != SUUNTO_SUCCESS) + return rc; + +#ifndef NDEBUG + message ("D9Write(0x%04x,%d,\"", address, len); + for (unsigned int i = 0; i < len; ++i) { + message ("%02x", data[i]); + } + message ("\");\n"); +#endif + + nbytes += len; + address += len; + data += len; + } + + return SUUNTO_SUCCESS; +} diff --git a/suunto_d9.h b/suunto_d9.h new file mode 100644 index 0000000..9ce671f --- /dev/null +++ b/suunto_d9.h @@ -0,0 +1,28 @@ +#ifndef SUUNTO_D9_H +#define SUUNTO_D9_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct d9 d9; + +#define SUUNTO_D9_MEMORY_SIZE 0x8000 +#define SUUNTO_D9_PACKET_SIZE 0x78 + +int suunto_d9_open (d9 **device, const char* name); + +int suunto_d9_close (d9 *device); + +int suunto_d9_read_version (d9 *device, unsigned char data[], unsigned int size); + +int suunto_d9_reset_maxdepth (d9 *device); + +int suunto_d9_read_memory (d9 *device, unsigned int address, unsigned char data[], unsigned int size); + +int suunto_d9_write_memory (d9 *device, unsigned int address, const unsigned char data[], unsigned int size); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* SUUNTO_D9_H */ diff --git a/suunto_d9_test.c b/suunto_d9_test.c new file mode 100644 index 0000000..a1d00d8 --- /dev/null +++ b/suunto_d9_test.c @@ -0,0 +1,323 @@ +#include // fopen, fwrite, fclose +#include // assert +#include // memcpy, memmove + +#include "suunto.h" +#include "utils.h" + +#define WARNING(expr) \ +{ \ + message ("%s:%d: %s\n", __FILE__, __LINE__, expr); \ +} + +#define DISTANCE(a,b) distance (a, b, SUUNTO_D9_MEMORY_SIZE - 0x019A) + +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 test_dump_sdm (const char* name, const char* filename) +{ + unsigned char data[SUUNTO_D9_MEMORY_SIZE] = {0}; + d9 *device = NULL; + + message ("suunto_d9_open\n"); + int rc = suunto_d9_open (&device, name); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Error opening serial port."); + return rc; + } + + message ("suunto_d9_read_version\n"); + unsigned char version[4] = {0}; + rc = suunto_d9_read_version (device, version, sizeof (version)); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot identify computer."); + suunto_d9_close (device); + return rc; + } + + message ("suunto_d9_read_memory\n"); + rc = suunto_d9_read_memory (device, 0x00, data + 0x00, SUUNTO_D9_PACKET_SIZE); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot read memory."); + suunto_d9_close (device); + return rc; + } + rc = suunto_d9_read_memory (device, 0x168, data + 0x168, SUUNTO_D9_PACKET_SIZE); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot read memory."); + suunto_d9_close (device); + return rc; + } + rc = suunto_d9_read_memory (device, 0xF0, data + 0xF0, SUUNTO_D9_PACKET_SIZE); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot read memory."); + suunto_d9_close (device); + return rc; + } + + message ("suunto_d9_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_D9_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_D9_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_d9_read_memory (device, address - len, package, len); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot read memory."); + suunto_d9_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_D9_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 ("D9Profile()=\""); + 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_d9_close\n"); + rc = suunto_d9_close (device); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot close device."); + return rc; + } + + return SUUNTO_SUCCESS; +} + +int test_dump_memory (const char* name, const char* filename) +{ + unsigned char data[SUUNTO_D9_MEMORY_SIZE] = {0}; + d9 *device = NULL; + + message ("suunto_d9_open\n"); + int rc = suunto_d9_open (&device, name); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Error opening serial port."); + return rc; + } + + message ("suunto_d9_read_version\n"); + unsigned char version[4] = {0}; + rc = suunto_d9_read_version (device, version, sizeof (version)); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot identify computer."); + suunto_d9_close (device); + return rc; + } + + message ("suunto_d9_read_memory\n"); + rc = suunto_d9_read_memory (device, 0x00, data, sizeof (data)); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot read memory."); + suunto_d9_close (device); + return rc; + } + + message ("Dumping data\n"); + FILE* fp = fopen (filename, "wb"); + if (fp != NULL) { + fwrite (data, sizeof (unsigned char), sizeof (data), fp); + fclose (fp); + } + + message ("suunto_d9_close\n"); + rc = suunto_d9_close (device); + if (rc != SUUNTO_SUCCESS) { + WARNING ("Cannot close device."); + return rc; + } + + return SUUNTO_SUCCESS; +} + +const char* errmsg (int rc) +{ + switch (rc) { + case SUUNTO_SUCCESS: + return "Success"; + case SUUNTO_ERROR: + return "Generic error"; + case SUUNTO_ERROR_IO: + return "Input/output error"; + case SUUNTO_ERROR_MEMORY: + return "Memory error"; + case SUUNTO_ERROR_PROTOCOL: + return "Protocol error"; + case SUUNTO_ERROR_TIMEOUT: + return "Timeout"; + default: + return "Unknown error"; + } +} + +int main(int argc, char *argv[]) +{ + message_set_logfile ("D9.LOG"); + +#ifdef _WIN32 + const char* name = "COM1"; +#else + const char* name = "/dev/ttyS0"; +#endif + + if (argc > 1) { + name = argv[1]; + } + + int a = test_dump_memory (name, "D9.DMP"); + int b = test_dump_sdm (name, "D9.SDM"); + + message ("\nSUMMARY\n"); + message ("-------\n"); + message ("test_dump_memory: %s\n", errmsg (a)); + message ("test_dump_sdm: %s\n", errmsg (b)); + + message_set_logfile (NULL); + + return 0; +}