diff --git a/src/descriptor.c b/src/descriptor.c index b774062..86f8daf 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -121,6 +121,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Reefnet", "Sensus Pro", DC_FAMILY_REEFNET_SENSUSPRO, 2}, {"Reefnet", "Sensus Ultra", DC_FAMILY_REEFNET_SENSUSULTRA, 3}, /* Oceanic VT Pro */ + {"Aeris", "500 AI", DC_FAMILY_OCEANIC_VTPRO, 0x4151}, {"Oceanic", "Versa Pro", DC_FAMILY_OCEANIC_VTPRO, 0x4155}, {"Aeris", "Atmos 2", DC_FAMILY_OCEANIC_VTPRO, 0x4158}, {"Oceanic", "Pro Plus 2", DC_FAMILY_OCEANIC_VTPRO, 0x4159}, diff --git a/src/oceanic_vtpro.c b/src/oceanic_vtpro.c index 0b37779..f0ea4ba 100644 --- a/src/oceanic_vtpro.c +++ b/src/oceanic_vtpro.c @@ -21,6 +21,7 @@ #include // memcpy #include // malloc, free +#include #include @@ -30,6 +31,7 @@ #include "serial.h" #include "ringbuffer.h" #include "checksum.h" +#include "array.h" #define ISINSTANCE(device) dc_device_isinstance((device), &oceanic_vtpro_device_vtable.base) @@ -40,6 +42,8 @@ #define NAK 0xA5 #define END 0x51 +#define AERIS500AI 0x4151 + typedef enum oceanic_vtpro_protocol_t { MOD, INTR, @@ -52,6 +56,7 @@ typedef struct oceanic_vtpro_device_t { oceanic_vtpro_protocol_t protocol; } oceanic_vtpro_device_t; +static dc_status_t oceanic_vtpro_device_logbook (dc_device_t *abstract, dc_event_progress_t *progress, dc_buffer_t *logbook); static dc_status_t oceanic_vtpro_device_read (dc_device_t *abstract, unsigned int address, unsigned char data[], unsigned int size); static dc_status_t oceanic_vtpro_device_close (dc_device_t *abstract); @@ -66,7 +71,7 @@ static const oceanic_common_device_vtable_t oceanic_vtpro_device_vtable = { oceanic_common_device_foreach, /* foreach */ oceanic_vtpro_device_close /* close */ }, - oceanic_common_device_logbook, + oceanic_vtpro_device_logbook, oceanic_common_device_profile, }; @@ -109,6 +114,18 @@ static const oceanic_common_layout_t oceanic_wisdom_layout = { 0 /* pt_mode_logbook */ }; +static const oceanic_common_layout_t aeris_500ai_layout = { + 0x20000, /* memsize */ + 0x0000, /* cf_devinfo */ + 0x0110, /* cf_pointers */ + 0x0200, /* rb_logbook_begin */ + 0x0200, /* rb_logbook_end */ + 8, /* rb_logbook_entry_size */ + 0x00200, /* rb_profile_begin */ + 0x20000, /* rb_profile_end */ + 0, /* pt_mode_global */ + 1 /* pt_mode_logbook */ +}; static dc_status_t oceanic_vtpro_send (oceanic_vtpro_device_t *device, const unsigned char command[], unsigned int csize) @@ -167,11 +184,13 @@ oceanic_vtpro_transfer (oceanic_vtpro_device_t *device, const unsigned char comm return rc; } - // Receive the answer of the dive computer. - status = dc_serial_read (device->port, answer, asize, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the answer."); - return status; + if (asize) { + // Receive the answer of the dive computer. + status = dc_serial_read (device->port, answer, asize, NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } } return DC_STATUS_SUCCESS; @@ -262,6 +281,105 @@ oceanic_vtpro_calibrate (oceanic_vtpro_device_t *device) return DC_STATUS_SUCCESS; } +static dc_status_t +oceanic_aeris500ai_device_logbook (dc_device_t *abstract, dc_event_progress_t *progress, dc_buffer_t *logbook) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + oceanic_vtpro_device_t *device = (oceanic_vtpro_device_t *) abstract; + + assert (device != NULL); + assert (device->base.layout != NULL); + assert (device->base.layout->rb_logbook_entry_size == PAGESIZE / 2); + assert (device->base.layout->rb_logbook_begin == device->base.layout->rb_logbook_end); + assert (progress != NULL); + + const oceanic_common_layout_t *layout = device->base.layout; + + // Erase the buffer. + if (!dc_buffer_clear (logbook)) + return DC_STATUS_NOMEMORY; + + // Read the pointer data. + unsigned char pointers[PAGESIZE] = {0}; + rc = oceanic_vtpro_device_read (abstract, layout->cf_pointers, pointers, sizeof (pointers)); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the memory page."); + return rc; + } + + // Get the logbook pointers. + unsigned int last = pointers[0x03]; + + // Update and emit a progress event. + progress->current += PAGESIZE; + progress->maximum += PAGESIZE + (last + 1) * PAGESIZE / 2; + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + + // Allocate memory for the logbook entries. + if (!dc_buffer_reserve (logbook, (last + 1) * PAGESIZE / 2)) + return DC_STATUS_NOMEMORY; + + // Send the logbook index command. + unsigned char command[] = {0x52, + (last >> 8) & 0xFF, // high + (last ) & 0xFF, // low + 0x00}; + rc = oceanic_vtpro_transfer (device, command, sizeof (command), NULL, 0); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the logbook index command."); + return rc; + } + + // Read the logbook index. + for (unsigned int i = 0; i < last + 1; ++i) { + // Receive the answer of the dive computer. + unsigned char answer[PAGESIZE / 2 + 1] = {0}; + rc = dc_serial_read (device->port, answer, sizeof(answer), NULL); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return rc; + } + + // Verify the checksum of the answer. + unsigned char crc = answer[PAGESIZE / 2]; + unsigned char ccrc = checksum_add_uint4 (answer, PAGESIZE / 2, 0x00); + if (crc != ccrc) { + ERROR (abstract->context, "Unexpected answer checksum."); + return DC_STATUS_PROTOCOL; + } + + // Update and emit a progress event. + progress->current += PAGESIZE / 2; + device_event_emit (abstract, DC_EVENT_PROGRESS, progress); + + // Ignore uninitialized entries. + if (array_isequal (answer, PAGESIZE / 2, 0xFF)) { + WARNING (abstract->context, "Uninitialized logbook entries detected!"); + continue; + } + + // Compare the fingerprint to identify previously downloaded entries. + if (memcmp (answer, device->base.fingerprint, PAGESIZE / 2) == 0) { + dc_buffer_clear (logbook); + } else { + dc_buffer_append (logbook, answer, PAGESIZE / 2); + } + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceanic_vtpro_device_logbook (dc_device_t *abstract, dc_event_progress_t *progress, dc_buffer_t *logbook) +{ + oceanic_vtpro_device_t *device = (oceanic_vtpro_device_t *) abstract; + + if (device->model == AERIS500AI) { + return oceanic_aeris500ai_device_logbook (abstract, progress, logbook); + } else { + return oceanic_common_device_logbook (abstract, progress, logbook); + } +} dc_status_t oceanic_vtpro_device_open (dc_device_t **out, dc_context_t *context, const char *name) @@ -295,7 +413,11 @@ oceanic_vtpro_device_open2 (dc_device_t **out, dc_context_t *context, const char // Set the default values. device->port = NULL; device->model = model; - device->protocol = MOD; + if (model == AERIS500AI) { + device->protocol = INTR; + } else { + device->protocol = MOD; + } // Open the device. status = dc_serial_open (&device->port, context, name); @@ -361,7 +483,9 @@ oceanic_vtpro_device_open2 (dc_device_t **out, dc_context_t *context, const char } // Override the base class values. - if (OCEANIC_COMMON_MATCH (device->base.version, oceanic_wisdom_version)) { + if (model == AERIS500AI) { + device->base.layout = &aeris_500ai_layout; + } else if (OCEANIC_COMMON_MATCH (device->base.version, oceanic_wisdom_version)) { device->base.layout = &oceanic_wisdom_layout; } else { device->base.layout = &oceanic_vtpro_layout; diff --git a/src/oceanic_vtpro_parser.c b/src/oceanic_vtpro_parser.c index 89d984d..859025e 100644 --- a/src/oceanic_vtpro_parser.c +++ b/src/oceanic_vtpro_parser.c @@ -31,6 +31,8 @@ #define ISINSTANCE(parser) dc_parser_isinstance((parser), &oceanic_vtpro_parser_vtable) +#define AERIS500AI 0x4151 + typedef struct oceanic_vtpro_parser_t oceanic_vtpro_parser_t; struct oceanic_vtpro_parser_t { @@ -109,27 +111,41 @@ oceanic_vtpro_parser_set_data (dc_parser_t *abstract, const unsigned char *data, static dc_status_t oceanic_vtpro_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { + oceanic_vtpro_parser_t *parser = (oceanic_vtpro_parser_t *) abstract; + if (abstract->size < 8) return DC_STATUS_DATAFORMAT; const unsigned char *p = abstract->data; if (datetime) { - // The logbook entry can only store the last digit of the year field, - // but the full year is also available in the dive header. - if (abstract->size < 40) - datetime->year = bcd2dec (p[4] & 0x0F) + 2000; - else - datetime->year = bcd2dec (((p[32 + 3] & 0xC0) >> 2) + ((p[32 + 2] & 0xF0) >> 4)) + 2000; - datetime->month = (p[4] & 0xF0) >> 4; - datetime->day = bcd2dec (p[3]); - datetime->hour = bcd2dec (p[1] & 0x7F); + // AM/PM bit of the 12-hour clock. + unsigned int pm = 0; + + if (parser->model == AERIS500AI) { + datetime->year = (p[2] & 0x0F) + 1999; + datetime->month = (p[3] & 0xF0) >> 4; + datetime->day = ((p[2] & 0xF0) >> 4) | ((p[3] & 0x02) << 3); + datetime->hour = bcd2dec (p[1] & 0x0F) + 10 * (p[3] & 0x01); + pm = p[3] & 0x08; + } else { + // The logbook entry can only store the last digit of the year field, + // but the full year is also available in the dive header. + if (abstract->size < 40) + datetime->year = bcd2dec (p[4] & 0x0F) + 2000; + else + datetime->year = bcd2dec (((p[32 + 3] & 0xC0) >> 2) + ((p[32 + 2] & 0xF0) >> 4)) + 2000; + datetime->month = (p[4] & 0xF0) >> 4; + datetime->day = bcd2dec (p[3]); + datetime->hour = bcd2dec (p[1] & 0x7F); + pm = p[1] & 0x80; + } datetime->minute = bcd2dec (p[0]); datetime->second = 0; // Convert to a 24-hour clock. datetime->hour %= 12; - if (p[1] & 0x80) + if (pm) datetime->hour += 12; } @@ -162,8 +178,17 @@ oceanic_vtpro_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns unsigned int footer = size - PAGESIZE; + unsigned int oxygen = 0; + unsigned int maxdepth = 0; unsigned int beginpressure = array_uint16_le(data + 0x26) & 0x0FFF; unsigned int endpressure = array_uint16_le(data + footer + 0x05) & 0x0FFF; + if (parser->model == AERIS500AI) { + oxygen = (array_uint16_le(data + footer + 2) & 0x0FF0) >> 4; + maxdepth = data[footer + 1]; + } else { + oxygen = data[footer + 3]; + maxdepth = array_uint16_le(data + footer + 0) & 0x0FFF; + } dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_tank_t *tank = (dc_tank_t *) value; @@ -174,15 +199,15 @@ oceanic_vtpro_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns *((unsigned int *) value) = parser->divetime; break; case DC_FIELD_MAXDEPTH: - *((double *) value) = (data[footer + 0] + ((data[footer + 1] & 0x0F) << 8)) * FEET; + *((double *) value) = maxdepth * FEET; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = 1; break; case DC_FIELD_GASMIX: gasmix->helium = 0.0; - if (data[footer + 3]) - gasmix->oxygen = data[footer + 3] / 100.0; + if (oxygen) + gasmix->oxygen = oxygen / 100.0; else gasmix->oxygen = 0.21; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; @@ -213,6 +238,8 @@ oceanic_vtpro_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns static dc_status_t oceanic_vtpro_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { + oceanic_vtpro_parser_t *parser = (oceanic_vtpro_parser_t *) abstract; + const unsigned char *data = abstract->data; unsigned int size = abstract->size; @@ -221,22 +248,18 @@ oceanic_vtpro_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ unsigned int time = 0; unsigned int interval = 0; - switch ((data[0x27] >> 4) & 0x07) { - case 0: - interval = 2; - break; - case 1: - interval = 15; - break; - case 2: - interval = 30; - break; - case 3: - interval = 60; - break; - default: - interval = 0; - break; + if (parser->model == AERIS500AI) { + const unsigned int intervals[] = {2, 5, 10, 15, 20, 25, 30}; + unsigned int samplerate = (data[0x27] >> 4); + if (samplerate >= 3 && samplerate <= 9) { + interval = intervals[samplerate - 3]; + } + } else { + const unsigned int intervals[] = {2, 15, 30, 60}; + unsigned int samplerate = (data[0x27] >> 4) & 0x07; + if (samplerate <= 3) { + interval = intervals[samplerate]; + } } // Initialize the state for the timestamp processing. @@ -323,12 +346,22 @@ oceanic_vtpro_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_ if (callback) callback (DC_SAMPLE_VENDOR, sample, userdata); // Depth (ft) - unsigned int depth = data[offset + 3]; + unsigned int depth = 0; + if (parser->model == AERIS500AI) { + depth = (array_uint16_le(data + offset + 2) & 0x0FF0) >> 4; + } else { + depth = data[offset + 3]; + } sample.depth = depth * FEET; if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); // Temperature (°F) - unsigned int temperature = data[offset + 6]; + unsigned int temperature = 0; + if (parser->model == AERIS500AI) { + temperature = (array_uint16_le(data + offset + 6) & 0x0FF0) >> 4; + } else { + temperature = data[offset + 6]; + } sample.temperature = (temperature - 32.0) * (5.0 / 9.0); if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);