From 11d9256e43c83cd051aedf599c36b5dda9502afe Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 19 Feb 2015 09:12:35 +0100 Subject: [PATCH 1/4] Use symbolic constants for the commands. --- src/oceanic_atom2.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 2301c35..d67bde6 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -43,6 +43,15 @@ rc == -1 ? DC_STATUS_IO : DC_STATUS_TIMEOUT \ ) +#define CMD_INIT 0xA8 +#define CMD_VERSION 0x84 +#define CMD_READ1 0xB1 +#define CMD_READ8 0xB4 +#define CMD_READ16 0xB8 +#define CMD_WRITE 0xB2 +#define CMD_KEEPALIVE 0x91 +#define CMD_QUIT 0x6A + #define ACK 0x5A #define NAK 0xA5 @@ -467,7 +476,7 @@ static dc_status_t oceanic_atom2_quit (oceanic_atom2_device_t *device) { // Send the command to the dive computer. - unsigned char command[4] = {0x6A, 0x05, 0xA5, 0x00}; + unsigned char command[4] = {CMD_QUIT, 0x05, 0xA5, 0x00}; dc_status_t rc = oceanic_atom2_send (device, command, sizeof (command), NAK); if (rc != DC_STATUS_SUCCESS) return rc; @@ -623,7 +632,7 @@ oceanic_atom2_device_keepalive (dc_device_t *abstract) return DC_STATUS_INVALIDARGS; // Send the command to the dive computer. - unsigned char command[4] = {0x91, 0x05, 0xA5, 0x00}; + unsigned char command[4] = {CMD_KEEPALIVE, 0x05, 0xA5, 0x00}; dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) return rc; @@ -644,7 +653,7 @@ oceanic_atom2_device_version (dc_device_t *abstract, unsigned char data[], unsig return DC_STATUS_INVALIDARGS; unsigned char answer[PAGESIZE + 1] = {0}; - unsigned char command[2] = {0x84, 0x00}; + unsigned char command[2] = {CMD_VERSION, 0x00}; dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), answer, sizeof (answer), 1); if (rc != DC_STATUS_SUCCESS) return rc; @@ -669,15 +678,15 @@ oceanic_atom2_device_read (dc_device_t *abstract, unsigned int address, unsigned unsigned int crc_size = 0; switch (device->bigpage) { case 1: - read_cmd = 0xB1; + read_cmd = CMD_READ1; crc_size = 1; break; case 8: - read_cmd = 0xB4; + read_cmd = CMD_READ8; crc_size = 1; break; case 16: - read_cmd = 0xB8; + read_cmd = CMD_READ16; crc_size = 2; break; default: @@ -739,7 +748,7 @@ oceanic_atom2_device_write (dc_device_t *abstract, unsigned int address, const u while (nbytes < size) { // Prepare to write the package. unsigned int number = address / PAGESIZE; - unsigned char prepare[4] = {0xB2, + unsigned char prepare[4] = {CMD_WRITE, (number >> 8) & 0xFF, // high (number ) & 0xFF, // low 0x00}; From f5d96a081c09d2fc515caaf99a299270cfb59ac6 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 16 Feb 2015 21:46:21 +0100 Subject: [PATCH 2/4] Extend the retry logic to corrupt data packets. At the moment, there is only retry logic for the ACK/NAK byte. Corrupt data packets (e.g. wrong checksum, unexpected length, etc) are treated as a fatal error. By reading the entire packet inside the retry loop, all non-fatal errors are automatically taken care off. This change is necessary for the Oceanic VTX. For some unknown reason, the device always responds with an invalid data packet when sending the version command for the first time: W: 84 00 R: 5A 4F ?? 45 41 4E 56 54 58 20 31 42 20 32 30 34 38 E9 There is always one byte missing (marked with question marks), resulting in a timeout. However, when re-sending the command again, we receive a valid data packet: W: 84 00 R: 5A 4F 43 45 41 4E 56 54 58 20 31 42 20 32 30 34 38 E9 This might be a firmware bug, because we're observing the exact same behaviour with the official Oceanlog application. --- src/oceanic_atom2.c | 77 ++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index d67bde6..ba4fe4f 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -378,8 +378,9 @@ static const oceanic_common_layout_t aeris_a300cs_layout = { 1 /* pt_mode_logbook */ }; + static dc_status_t -oceanic_atom2_send (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char ack) +oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size) { dc_device_t *abstract = (dc_device_t *) device; @@ -397,6 +398,12 @@ oceanic_atom2_send (oceanic_atom2_device_t *device, const unsigned char command[ return EXITCODE (n); } + // Get the correct ACK byte. + unsigned int ack = ACK; + if (command[0] == CMD_INIT || command[0] == CMD_QUIT) { + ack = NAK; + } + // Receive the response (ACK/NAK) of the dive computer. unsigned char response = 0; n = serial_read (device->port, &response, 1); @@ -411,40 +418,6 @@ oceanic_atom2_send (oceanic_atom2_device_t *device, const unsigned char command[ return DC_STATUS_PROTOCOL; } - return DC_STATUS_SUCCESS; -} - - -static dc_status_t -oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size) -{ - dc_device_t *abstract = (dc_device_t *) device; - - // Send the command to the device. If the device responds with an - // ACK byte, the command was received successfully and the answer - // (if any) follows after the ACK byte. If the device responds with - // a NAK byte, we try to resend the command a number of times before - // returning an error. - - unsigned int nretries = 0; - dc_status_t rc = DC_STATUS_SUCCESS; - while ((rc = oceanic_atom2_send (device, command, csize, ACK)) != 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; - - // Increase the inter packet delay. - if (device->delay < MAXDELAY) - device->delay++; - - // Delay the next attempt. - serial_sleep (device->port, 100); - serial_flush (device->port, SERIAL_QUEUE_INPUT); - } - if (asize) { // Receive the answer of the dive computer. int n = serial_read (device->port, answer, asize); @@ -472,12 +445,44 @@ oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char comm } +static dc_status_t +oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size) +{ + // Send the command to the device. If the device responds with an + // ACK byte, the command was received successfully and the answer + // (if any) follows after the ACK byte. If the device responds with + // a NAK byte, we try to resend the command a number of times before + // returning an error. + + unsigned int nretries = 0; + dc_status_t rc = DC_STATUS_SUCCESS; + while ((rc = oceanic_atom2_packet (device, command, csize, answer, asize, crc_size)) != 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; + + // Increase the inter packet delay. + if (device->delay < MAXDELAY) + device->delay++; + + // Delay the next attempt. + serial_sleep (device->port, 100); + serial_flush (device->port, SERIAL_QUEUE_INPUT); + } + + return DC_STATUS_SUCCESS; +} + + static dc_status_t oceanic_atom2_quit (oceanic_atom2_device_t *device) { // Send the command to the dive computer. unsigned char command[4] = {CMD_QUIT, 0x05, 0xA5, 0x00}; - dc_status_t rc = oceanic_atom2_send (device, command, sizeof (command), NAK); + dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) return rc; From 4dfca21a2bd9f95ff417a58312c8f1e1ed88eb9a Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 19 Feb 2015 10:37:05 +0100 Subject: [PATCH 3/4] Pass the model number to the open function. This change will be necessary for the Oceanic VTX, because it uses a higher baudrate. To maintain backwards compatibility, the existing function remains unchanged and a new function is introduced instead. --- include/libdivecomputer/oceanic_atom2.h | 3 +++ src/device.c | 2 +- src/libdivecomputer.symbols | 1 + src/oceanic_atom2.c | 8 ++++++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/include/libdivecomputer/oceanic_atom2.h b/include/libdivecomputer/oceanic_atom2.h index 4c52420..4cae707 100644 --- a/include/libdivecomputer/oceanic_atom2.h +++ b/include/libdivecomputer/oceanic_atom2.h @@ -33,6 +33,9 @@ extern "C" { dc_status_t oceanic_atom2_device_open (dc_device_t **device, dc_context_t *context, const char *name); +dc_status_t +oceanic_atom2_device_open2 (dc_device_t **device, dc_context_t *context, const char *name, unsigned int model); + dc_status_t oceanic_atom2_device_version (dc_device_t *device, unsigned char data[], unsigned int size); diff --git a/src/device.c b/src/device.c index 1bac4f4..d95585d 100644 --- a/src/device.c +++ b/src/device.c @@ -115,7 +115,7 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr rc = oceanic_veo250_device_open (&device, context, name); break; case DC_FAMILY_OCEANIC_ATOM2: - rc = oceanic_atom2_device_open (&device, context, name); + rc = oceanic_atom2_device_open2 (&device, context, name, dc_descriptor_get_model (descriptor)); break; case DC_FAMILY_MARES_NEMO: rc = mares_nemo_device_open (&device, context, name); diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 8109f96..f27f6fd 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -92,6 +92,7 @@ mares_darwin_extract_dives mares_iconhd_device_open mares_iconhd_extract_dives oceanic_atom2_device_open +oceanic_atom2_device_open2 oceanic_atom2_device_version oceanic_atom2_device_keepalive oceanic_veo250_device_open diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index ba4fe4f..656812c 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -492,6 +492,14 @@ oceanic_atom2_quit (oceanic_atom2_device_t *device) dc_status_t oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, const char *name) +{ + return oceanic_atom2_device_open2 (out, context, name, 0); +} + + +dc_status_t +oceanic_atom2_device_open2 (dc_device_t **out, dc_context_t *context, const char *name, unsigned int model) + { if (out == NULL) return DC_STATUS_INVALIDARGS; From bad4dd43426ff4ecbfad77ecbe263a84466609d4 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Wed, 24 Dec 2014 09:06:35 +0100 Subject: [PATCH 4/4] Add support for the Oceanic VTX. The Oceanic VTX is very similar to the Aeris A300 CS. The main difference is the higher baudrate (115200 vs 38400). This is annoying because without the correct baudrate, no communication is possible and thus the existing autodetection based on the version packet doesn't work. As a workaround, we now rely on the model number from the device descriptor. The consequence is that the user must select the correct model now (or at least a model with the same baudrate). --- src/descriptor.c | 1 + src/oceanic_atom2.c | 11 ++++++++++- src/oceanic_atom2_parser.c | 25 ++++++++++++++----------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index d6d75d4..85f6622 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -178,6 +178,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Aeris", "F11", DC_FAMILY_OCEANIC_ATOM2, 0x4549}, {"Oceanic", "OCi", DC_FAMILY_OCEANIC_ATOM2, 0x454B}, {"Aeris", "A300CS", DC_FAMILY_OCEANIC_ATOM2, 0x454C}, + {"Oceanic", "VTX", DC_FAMILY_OCEANIC_ATOM2, 0x4557}, /* Mares Nemo */ {"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0}, {"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0}, diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 656812c..25e9d9f 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -34,6 +34,8 @@ #define ISINSTANCE(device) dc_device_isinstance((device), &oceanic_atom2_device_vtable) +#define VTX 0x4557 + #define MAXRETRIES 2 #define MAXDELAY 16 #define INVALID 0xFFFFFFFF @@ -168,6 +170,7 @@ static const oceanic_common_version_t oceanic_reactpro_version[] = { static const oceanic_common_version_t aeris_a300cs_version[] = { {"AER300CS \0\0 2048"}, + {"OCEANVTX \0\0 2048"}, }; static const oceanic_common_layout_t aeris_f10_layout = { @@ -529,8 +532,14 @@ oceanic_atom2_device_open2 (dc_device_t **out, dc_context_t *context, const char return DC_STATUS_IO; } + // Get the correct baudrate. + unsigned int baudrate = 38400; + if (model == VTX) { + baudrate = 115200; + } + // Set the serial communication protocol (38400 8N1). - rc = serial_configure (device->port, 38400, 8, SERIAL_PARITY_NONE, 1, SERIAL_FLOWCONTROL_NONE); + rc = serial_configure (device->port, baudrate, 8, SERIAL_PARITY_NONE, 1, SERIAL_FLOWCONTROL_NONE); if (rc == -1) { ERROR (context, "Failed to set the terminal attributes."); serial_close (device->port); diff --git a/src/oceanic_atom2_parser.c b/src/oceanic_atom2_parser.c index fc68ecb..491e74b 100644 --- a/src/oceanic_atom2_parser.c +++ b/src/oceanic_atom2_parser.c @@ -72,6 +72,7 @@ #define F11 0x4549 #define OCI 0x454B #define A300CS 0x454C +#define VTX 0x4557 #define NORMAL 0 #define GAUGE 1 @@ -227,6 +228,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim datetime->minute = p[10]; break; case A300CS: + case VTX: datetime->year = (p[10]) + 2000; datetime->month = (p[8]); datetime->day = (p[9]); @@ -381,7 +383,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns *((unsigned int *) value) = 4; } else if (parser->model == TX1) { *((unsigned int *) value) = 6; - } else if (parser->model == A300CS) { + } else if (parser->model == A300CS || parser->model == VTX) { if (data[0x39] & 0x04) { *((unsigned int *) value) = 1; } else if (data[0x39] & 0x08) { @@ -400,7 +402,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns oxygen = data[header + 3]; } else if (parser->model == OCI) { oxygen = data[0x28 + flags]; - } else if (parser->model == A300CS) { + } else if (parser->model == A300CS || parser->model == VTX) { oxygen = data[0x2A + flags]; } else if (parser->model == TX1) { oxygen = data[0x3E + flags]; @@ -413,7 +415,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_SALINITY: - if (parser->model == A300CS) { + if (parser->model == A300CS || parser->model == VTX) { if (data[0x18] & 0x80) { water->type = DC_WATER_FRESH; } else { @@ -478,7 +480,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ } else if (parser->model == F11) { headersize = 5 * PAGESIZE; footersize = PAGESIZE / 2; - } else if (parser->model == A300CS) { + } else if (parser->model == A300CS || parser->model == VTX) { headersize = 5 * PAGESIZE; } @@ -504,7 +506,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ unsigned int interval = 1; if (mode != FREEDIVE) { unsigned int idx = 0x17; - if (parser->model == A300CS) + if (parser->model == A300CS || parser->model == VTX) idx = 0x1f; switch (data[idx] & 0x03) { case 0: @@ -531,7 +533,8 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ } } else if (parser->model == OC1A || parser->model == OC1B || parser->model == OC1C || parser->model == OCI || - parser->model == TX1 || parser->model == A300CS) { + parser->model == TX1 || parser->model == A300CS || + parser->model == VTX) { samplesize = PAGESIZE; } @@ -557,7 +560,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ unsigned int pressure = 0; if (have_pressure) { unsigned int idx = 2; - if (parser->model == A300CS) + if (parser->model == A300CS || parser->model == VTX) idx = 16; pressure = data[header + idx] + (data[header + idx + 1] << 8); if (pressure == 10000) @@ -611,7 +614,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ // Tank pressure (1 psi) and number tank = 0; pressure = (((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF); - } else if (parser->model == A300CS) { + } else if (parser->model == A300CS || parser->model == VTX) { // Tank pressure (1 psi) and number (one based index) tank = (data[offset + 1] & 0x03) - 1; pressure = ((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF; @@ -656,7 +659,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ temperature = data[offset + 1]; } else if (parser->model == VT4 || parser->model == VT41 || parser->model == ATOM3 || parser->model == ATOM31 || parser->model == A300AI) { temperature = ((data[offset + 7] & 0xF0) >> 4) | ((data[offset + 7] & 0x0C) << 2) | ((data[offset + 5] & 0x0C) << 4); - } else if (parser->model == A300CS) { + } else if (parser->model == A300CS || parser->model == VTX) { temperature = data[offset + 11]; } else { unsigned int sign; @@ -690,7 +693,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ parser->model == ZENAIR ||parser->model == A300AI || parser->model == DG03 || parser->model == PROPLUS3) pressure = (((data[offset + 0] & 0x03) << 8) + data[offset + 1]) * 5; - else if (parser->model == TX1 || parser->model == A300CS) + else if (parser->model == TX1 || parser->model == A300CS || parser->model == VTX) pressure = array_uint16_le (data + offset + 4); else pressure -= data[offset + 1]; @@ -718,7 +721,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ // NDL / Deco // bits 6..4 of byte 15 encode deco state & depth // bytes 6 & 7 encode minutes of NDL / deco - if (parser->model == A300CS) { + if (parser->model == A300CS || parser->model == VTX) { unsigned int deco = (data[offset + 15] & 0x70) >> 4; if (deco) { sample.deco.type = DC_DECO_DECOSTOP;