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:
parent
9c251b6814
commit
aaa7fbe08d
@ -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},
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user