Add support for the Aeris 500AI.

The Aeris 500AI is quite different from the other vtpro compatible
models. First, it uses the INTR protocol variant. Next, it doesn't
appear to have a logbook ringbuffer. Instead it supports a new read
logbook index command (0x52) that returns the logbook entries. This
requires a custom implementation of the logbook function.
This commit is contained in:
Jef Driesen 2015-07-15 21:38:01 +02:00
parent 9c251b6814
commit aaa7fbe08d
3 changed files with 197 additions and 39 deletions

View File

@ -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},

View File

@ -21,6 +21,7 @@
#include <string.h> // memcpy
#include <stdlib.h> // malloc, free
#include <assert.h>
#include <libdivecomputer/oceanic_vtpro.h>
@ -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;

View File

@ -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);