Add support for the Deepblu Cosmiq+ dive computer

Somewhat basic support, but the data the Deepblu Cosmiq+ reports is
pretty basic.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Linus Torvalds 2020-05-07 13:09:46 -07:00
parent 4e9e94d9f8
commit 8594445088
10 changed files with 861 additions and 0 deletions

View File

@ -91,6 +91,7 @@ static const backend_table_t g_backends[] = {
{"cochran", DC_FAMILY_COCHRAN_COMMANDER, 0},
{"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0},
{"descentmk1", DC_FAMILY_GARMIN, 0},
{"cosmiq", DC_FAMILY_DEEPBLU, 0},
};
static const transport_table_t g_transports[] = {

View File

@ -110,6 +110,8 @@ typedef enum dc_family_t {
DC_FAMILY_TECDIVING_DIVECOMPUTEREU = (15 << 16),
/* Garmin */
DC_FAMILY_GARMIN = (16 << 16),
/* Deepblu */
DC_FAMILY_DEEPBLU = (17 << 16),
} dc_family_t;
#ifdef __cplusplus

View File

@ -506,6 +506,14 @@
RelativePath="..\src\garmin_parser.c"
>
</File>
<File
RelativePath="..\src\deepblu.c"
>
</File>
<File
RelativePath="..\src\deepblu_parser.c"
>
</File>
<File
RelativePath="..\src\field-cache.c"
>
@ -856,6 +864,10 @@
RelativePath="..\src\garmin.h"
>
</File>
<File
RelativePath="..\src\deepblu.h"
>
</File>
<File
RelativePath="..\src\timer.h"
>

View File

@ -74,6 +74,7 @@ libdivecomputer_la_SOURCES = \
cochran_commander.h cochran_commander.c cochran_commander_parser.c \
tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \
garmin.h garmin.c garmin_parser.c \
deepblu.h deepblu.c deepblu_parser.c \
socket.h socket.c \
irda.c \
usbhid.c \

500
src/deepblu.c Normal file
View File

@ -0,0 +1,500 @@
/*
* Deepblu Cosmiq+ downloading
*
* Copyright (C) 2019 Linus Torvalds
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <string.h>
#include <stdlib.h>
#include "deepblu.h"
#include "context-private.h"
#include "device-private.h"
#include "array.h"
// "Write state"
#define CMD_SETTIME 0x20 // Send 6 byte date-time, get single-byte 00x00 ack
#define CMD_23 0x23 // Send 00/01 byte, get ack back? Some metric/imperial setting?
// "Read dives"?
#define CMD_GETDIVENR 0x40 // Send empty byte, get single-byte number of dives back
#define CMD_GETDIVE 0x41 // Send dive number (1-nr) byte, get dive stat length byte back
#define RSP_DIVESTAT 0x42 // .. followed by packets of dive stat for that dive of that length
#define CMD_GETPROFILE 0x43 // Send dive number (1-nr) byte, get dive profile length BE word back
#define RSP_DIVEPROF 0x44 // .. followed by packets of dive profile of that length
// "Read state"
#define CMD_GETTIME 0x50 // Send empty byte, get six-byte bcd date-time back
#define CMD_51 0x51 // Send empty byte, get four bytes back (03 dc 00 e3)
#define CMD_52 0x52 // Send empty byte, get two bytes back (bf 8d)
#define CMD_53 0x53 // Send empty byte, get six bytes back (0e 81 00 03 00 00)
#define CMD_54 0x54 // Send empty byte, get byte back (00)
#define CMD_55 0x55 // Send empty byte, get byte back (00)
#define CMD_56 0x56 // Send empty byte, get byte back (00)
#define CMD_57 0x57 // Send empty byte, get byte back (00)
#define CMD_58 0x58 // Send empty byte, get byte back (52)
#define CMD_59 0x59 // Send empty byte, get six bytes back (00 00 07 00 00 00)
// (00 00 00 00 00 00)
#define CMD_5a 0x5a // Send empty byte, get six bytes back (23 1b 09 d8 37 c0)
#define CMD_5b 0x5b // Send empty byte, get six bytes back (00 21 00 14 00 01)
// (00 00 00 14 00 01)
#define CMD_5c 0x5c // Send empty byte, get six bytes back (13 88 00 46 20 00)
// (13 88 00 3c 15 00)
#define CMD_5d 0x5d // Send empty byte, get six bytes back (19 00 23 0C 02 0E)
// (14 14 14 0c 01 0e)
#define CMD_5f 0x5f // Send empty byte, get six bytes back (00 00 07 00 00 00)
#define COSMIQ_HDR_SIZE 36
typedef struct deepblu_device_t {
dc_device_t base;
dc_iostream_t *iostream;
unsigned char fingerprint[COSMIQ_HDR_SIZE];
} deepblu_device_t;
static dc_status_t deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
static dc_status_t deepblu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
static dc_status_t deepblu_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime);
static dc_status_t deepblu_device_close (dc_device_t *abstract);
static const dc_device_vtable_t deepblu_device_vtable = {
sizeof(deepblu_device_t),
DC_FAMILY_DEEPBLU,
deepblu_device_set_fingerprint, /* set_fingerprint */
NULL, /* read */
NULL, /* write */
NULL, /* dump */
deepblu_device_foreach, /* foreach */
deepblu_device_timesync, /* timesync */
deepblu_device_close, /* close */
};
// Maximum data in a packet. It's actually much
// less than this, since BLE packets are small and
// with the 7 bytes of headers and final newline
// and the HEX encoding, the actual maximum is
// just something like 6 bytes.
//
// But in theory the data could be done over
// multiple packets. That doesn't seem to be
// the case in anything I've seen so far.
//
// Pick something small and easy to use for
// stack buffers.
#define MAX_DATA 20
static char *
write_hex_byte(unsigned char data, char *p)
{
static const char hex[16] = "0123456789ABCDEF";
*p++ = hex[data >> 4];
*p++ = hex[data & 0xf];
return p;
}
//
// Send a cmd packet.
//
// The format of the cmd on the "wire" is:
// - byte '#'
// - HEX char of cmd
// - HEX char two's complement modular sum of packet data (including cmd/size)
// - HEX char size of data as encoded in HEX
// - n * HEX char data
// - byte '\n'
// so you end up having 8 bytes of header/trailer overhead, and two bytes
// for every byte of data sent due to the HEX encoding.
//
static dc_status_t
deepblu_send_cmd(deepblu_device_t *device, const unsigned char cmd, const unsigned char data[], size_t size)
{
char buffer[8+2*MAX_DATA], *p;
unsigned char csum;
int i;
if (size > MAX_DATA)
return DC_STATUS_INVALIDARGS;
// Calculate packet csum
csum = cmd + 2*size;
for (i = 0; i < size; i++)
csum += data[i];
csum = -csum;
// Fill the data buffer
p = buffer;
*p++ = '#';
p = write_hex_byte(cmd, p);
p = write_hex_byte(csum, p);
p = write_hex_byte(size*2, p);
for (i = 0; i < size; i++)
p = write_hex_byte(data[i], p);
*p++ = '\n';
// .. and send it out
return dc_iostream_write(device->iostream, buffer, p-buffer, NULL);
}
//
// Receive one 'line' of data
//
// The deepblu BLE protocol is ASCII line based and packetized.
// Normally one packet is one line, but it looks like the Nordic
// Semi BLE chip will sometimes send packets early (some internal
// serial buffer timeout?) with incompete data.
//
// So read packets until you get newline.
static dc_status_t
deepblu_recv_line(deepblu_device_t *device, unsigned char *buf, size_t size)
{
while (1) {
unsigned char buffer[20];
size_t transferred = 0;
dc_status_t status;
status = dc_iostream_read(device->iostream, buffer, sizeof(buffer), &transferred);
if (status != DC_STATUS_SUCCESS) {
ERROR(device->base.context, "Failed to receive Deepblu reply packet.");
return status;
}
if (transferred > size) {
ERROR(device->base.context, "Deepblu reply packet with too much data (got %zu, expected %zu)", transferred, size);
return DC_STATUS_IO;
}
if (!transferred) {
ERROR(device->base.context, "Empty Deepblu reply packet");
return DC_STATUS_IO;
}
memcpy(buf, buffer, transferred);
buf += transferred;
size -= transferred;
if (buf[-1] == '\n')
break;
}
buf[-1] = 0;
return DC_STATUS_SUCCESS;
}
static int
hex_nibble(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int
read_hex_byte(char *p)
{
// This is negative if either of the nibbles is invalid
return (hex_nibble(p[0]) << 4) | hex_nibble(p[1]);
}
//
// Receive a reply packet
//
// The reply packet has the same format as the cmd packet we
// send, except the first byte is '$' instead of '#'.
static dc_status_t
deepblu_recv_data(deepblu_device_t *device, const unsigned char expected, unsigned char *buf, size_t size, size_t *received)
{
int len, i;
dc_status_t status;
char buffer[8+2*MAX_DATA];
int cmd, csum, ndata;
status = deepblu_recv_line(device, buffer, sizeof(buffer));
if (status != DC_STATUS_SUCCESS)
return status;
// deepblu_recv_line() always zero-terminates the result
// if it returned success, and has removed the final newline.
len = strlen(buffer);
HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "rcv", buffer, len);
// A valid reply should always be at least 7 characters: the
// initial '$' and the three header HEX bytes.
if (len < 8 || buffer[0] != '$') {
ERROR(device->base.context, "Invalid Deepblu reply packet");
return DC_STATUS_IO;
}
cmd = read_hex_byte(buffer+1);
csum = read_hex_byte(buffer+3);
ndata = read_hex_byte(buffer+5);
if ((cmd | csum | ndata) < 0) {
ERROR(device->base.context, "non-hex Deepblu reply packet header");
return DC_STATUS_IO;
}
// Verify the data length: it's the size of the HEX data,
// and should also match the line length we got (the 7
// is for the header data we already decoded above).
if ((ndata & 1) || ndata != len - 7) {
ERROR(device->base.context, "Deepblu reply packet data length does not match (claimed %d, got %d)", ndata, len-7);
return DC_STATUS_IO;
}
if (ndata >> 1 > size) {
ERROR(device->base.context, "Deepblu reply packet too big for buffer (ndata=%d, size=%zu)", ndata, size);
return DC_STATUS_IO;
}
csum += cmd + ndata;
for (i = 7; i < len; i += 2) {
int byte = read_hex_byte(buffer + i);
if (byte < 0) {
ERROR(device->base.context, "Deepblu reply packet data not valid hex");
return DC_STATUS_IO;
}
*buf++ = byte;
csum += byte;
}
if (csum & 255) {
ERROR(device->base.context, "Deepblu reply packet csum not valid (%x)", csum);
return DC_STATUS_IO;
}
*received = ndata >> 1;
return DC_STATUS_SUCCESS;
}
// Common communication pattern: send a command, expect data back with the same
// command byte.
static dc_status_t
deepblu_send_recv(deepblu_device_t *device, const unsigned char cmd,
const unsigned char *data, size_t data_size,
unsigned char *result, size_t result_size)
{
dc_status_t status;
size_t got;
status = deepblu_send_cmd(device, cmd, data, data_size);
if (status != DC_STATUS_SUCCESS)
return status;
status = deepblu_recv_data(device, cmd, result, result_size, &got);
if (status != DC_STATUS_SUCCESS)
return status;
if (got != result_size) {
ERROR(device->base.context, "Deepblu result size didn't match expected (expected %zu, got %zu)",
result_size, got);
return DC_STATUS_IO;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepblu_recv_bulk(deepblu_device_t *device, const unsigned char cmd, unsigned char *buf, size_t len)
{
while (len) {
dc_status_t status;
size_t got;
status = deepblu_recv_data(device, cmd, buf, len, &got);
if (status != DC_STATUS_SUCCESS)
return status;
if (got > len) {
ERROR(device->base.context, "Deepblu bulk receive overflow");
return DC_STATUS_IO;
}
buf += got;
len -= got;
}
return DC_STATUS_SUCCESS;
}
dc_status_t
deepblu_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
{
deepblu_device_t *device;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
device = (deepblu_device_t *) dc_device_allocate (context, &deepblu_device_vtable);
if (device == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
// Set the default values.
device->iostream = iostream;
memset(device->fingerprint, 0, sizeof(device->fingerprint));
*out = (dc_device_t *) device;
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
{
deepblu_device_t *device = (deepblu_device_t *)abstract;
HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "set_fingerprint", data, size);
if (size && size != sizeof (device->fingerprint))
return DC_STATUS_INVALIDARGS;
if (size)
memcpy (device->fingerprint, data, sizeof (device->fingerprint));
else
memset (device->fingerprint, 0, sizeof (device->fingerprint));
return DC_STATUS_SUCCESS;
}
static unsigned char bcd(int val)
{
if (val >= 0 && val < 100) {
int high = val / 10;
int low = val % 10;
return (high << 4) | low;
}
return 0;
}
static dc_status_t
deepblu_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime)
{
deepblu_device_t *device = (deepblu_device_t *)abstract;
unsigned char result[1], data[6];
dc_status_t status;
size_t len;
data[0] = bcd(datetime->year - 2000);
data[1] = bcd(datetime->month);
data[2] = bcd(datetime->day);
data[3] = bcd(datetime->hour);
data[4] = bcd(datetime->minute);
data[5] = bcd(datetime->second);
// Maybe also check that we received one zero byte (ack?)
return deepblu_send_recv(device, CMD_SETTIME,
data, sizeof(data),
result, sizeof(result));
}
static dc_status_t
deepblu_device_close (dc_device_t *abstract)
{
deepblu_device_t *device = (deepblu_device_t *) abstract;
return DC_STATUS_SUCCESS;
}
static const char zero[MAX_DATA];
static dc_status_t
deepblu_download_dive(deepblu_device_t *device, unsigned char nr, dc_dive_callback_t callback, void *userdata)
{
unsigned char header_len;
unsigned char profilebytes[2];
unsigned int profile_len;
dc_status_t status;
char header[256];
unsigned char *profile;
status = deepblu_send_recv(device, CMD_GETDIVE, &nr, 1, &header_len, 1);
if (status != DC_STATUS_SUCCESS)
return status;
status = deepblu_recv_bulk(device, RSP_DIVESTAT, header, header_len);
if (status != DC_STATUS_SUCCESS)
return status;
memset(header + header_len, 0, 256 - header_len);
/* The header is the fingerprint. If we've already seen this header, we're done */
if (memcmp(header, device->fingerprint, sizeof (device->fingerprint)) == 0)
return DC_STATUS_DONE;
status = deepblu_send_recv(device, CMD_GETPROFILE, &nr, 1, profilebytes, sizeof(profilebytes));
if (status != DC_STATUS_SUCCESS)
return status;
profile_len = (profilebytes[0] << 8) | profilebytes[1];
profile = malloc(256 + profile_len);
if (!profile) {
ERROR (device->base.context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY;
}
// We make the dive data be 256 bytes of header, followed by the profile data
memcpy(profile, header, 256);
status = deepblu_recv_bulk(device, RSP_DIVEPROF, profile+256, profile_len);
if (status != DC_STATUS_SUCCESS)
return status;
if (callback) {
if (!callback(profile, profile_len+256, header, header_len, userdata))
return DC_STATUS_DONE;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepblu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
deepblu_device_t *device = (deepblu_device_t *) abstract;
unsigned char nrdives, val;
dc_status_t status;
int i;
val = 0;
status = deepblu_send_recv(device, CMD_GETDIVENR, &val, 1, &nrdives, 1);
if (status != DC_STATUS_SUCCESS)
return status;
if (!nrdives)
return DC_STATUS_SUCCESS;
progress.maximum = nrdives;
progress.current = 0;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
for (i = 1; i <= nrdives; i++) {
if (device_is_cancelled(abstract)) {
dc_status_set_error(&status, DC_STATUS_CANCELLED);
break;
}
status = deepblu_download_dive(device, i, callback, userdata);
switch (status) {
case DC_STATUS_DONE:
i = nrdives;
break;
case DC_STATUS_SUCCESS:
break;
default:
return status;
}
progress.current = i;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
}
return DC_STATUS_SUCCESS;
}

43
src/deepblu.h Normal file
View File

@ -0,0 +1,43 @@
/*
* Deepblu Cosmiq+ downloading/parsing
*
* Copyright (C) 2018 Linus Torvalds
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#ifndef DEEPBLU_H
#define DEEPBLU_H
#include <libdivecomputer/context.h>
#include <libdivecomputer/iostream.h>
#include <libdivecomputer/device.h>
#include <libdivecomputer/parser.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
dc_status_t
deepblu_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t
deepblu_parser_create (dc_parser_t **parser, dc_context_t *context);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* DEEPBLU_H */

278
src/deepblu_parser.c Normal file
View File

@ -0,0 +1,278 @@
/*
* Deeplu Cosmiq+ parsing
*
* Copyright (C) 2019 Linus Torvalds
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "deepblu.h"
#include "context-private.h"
#include "parser-private.h"
#include "array.h"
#include "field-cache.h"
#define C_ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
#define MAXFIELDS 128
struct msg_desc;
typedef struct deepblu_parser_t {
dc_parser_t base;
dc_sample_callback_t callback;
void *userdata;
// 20 sec for scuba, 1 sec for freedives
int sample_interval;
// Common fields
struct dc_field_cache cache;
} deepblu_parser_t;
static dc_status_t deepblu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
static dc_status_t deepblu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
static dc_status_t deepblu_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
static dc_status_t deepblu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
static const dc_parser_vtable_t deepblu_parser_vtable = {
sizeof(deepblu_parser_t),
DC_FAMILY_DEEPBLU,
deepblu_parser_set_data, /* set_data */
deepblu_parser_get_datetime, /* datetime */
deepblu_parser_get_field, /* fields */
deepblu_parser_samples_foreach, /* samples_foreach */
NULL /* destroy */
};
dc_status_t
deepblu_parser_create (dc_parser_t **out, dc_context_t *context)
{
deepblu_parser_t *parser = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
parser = (deepblu_parser_t *) dc_parser_allocate (context, &deepblu_parser_vtable);
if (parser == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
*out = (dc_parser_t *) parser;
return DC_STATUS_SUCCESS;
}
static double
pressure_to_depth(unsigned int mbar)
{
// Specific weight of seawater (millibar to cm)
const double specific_weight = 1.024 * 0.980665;
// Absolute pressure, subtract surface pressure
if (mbar < 1013)
return 0.0;
mbar -= 1013;
return mbar / specific_weight / 100.0;
}
static dc_status_t
deepblu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
{
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
const unsigned char *hdr = data;
const unsigned char *profile = data + 256;
unsigned int divetime, maxpressure;
dc_gasmix_t gasmix = {0, };
if (size < 256)
return DC_STATUS_IO;
deepblu->callback = NULL;
deepblu->userdata = NULL;
memset(&deepblu->cache, 0, sizeof(deepblu->cache));
// LE16 at 0 is 'dive number'
// LE16 at 12 is the dive time
// It's in seconds for freedives, minutes for scuba/gauge
divetime = hdr[12] + 256*hdr[13];
// Byte at 2 is 'activity type' (2 = scuba, 3 = gauge, 4 = freedive)
// Byte at 3 is O2 percentage
switch (data[2]) {
case 2:
// SCUBA - divetime in minutes
divetime *= 60;
gasmix.oxygen = data[3] / 100.0;
DC_ASSIGN_IDX(deepblu->cache, GASMIX, 0, gasmix);
DC_ASSIGN_FIELD(deepblu->cache, GASMIX_COUNT, 1);
DC_ASSIGN_FIELD(deepblu->cache, DIVEMODE, DC_DIVEMODE_OC);
break;
case 3:
// GAUGE - divetime in minutes
divetime *= 60;
DC_ASSIGN_FIELD(deepblu->cache, DIVEMODE, DC_DIVEMODE_GAUGE);
break;
case 4:
// FREEDIVE - divetime in seconds
DC_ASSIGN_FIELD(deepblu->cache, DIVEMODE, DC_DIVEMODE_FREEDIVE);
deepblu->sample_interval = 1;
break;
default:
ERROR (abstract->context, "Deepblu: unknown activity type '%02x'", data[2]);
break;
}
// Seems to be fixed at 20s for scuba, 1s for freedive
deepblu->sample_interval = hdr[26];
maxpressure = hdr[22] + 256*hdr[23]; // Maxpressure in millibar
DC_ASSIGN_FIELD(deepblu->cache, DIVETIME, divetime);
DC_ASSIGN_FIELD(deepblu->cache, MAXDEPTH, pressure_to_depth(maxpressure));
return DC_STATUS_SUCCESS;
}
// The layout of the header in the 'data' is
// 0: LE16 dive number
// 2: dive type byte?
// 3: O2 percentage byte
// 4: unknown
// 5: unknown
// 6: LE16 year
// 8: day of month
// 9: month
// 10: minute
// 11: hour
// 12: LE16 dive time
// 14: LE16 ??
// 16: LE16 surface pressure?
// 18: LE16 ??
// 20: LE16 ??
// 22: LE16 max depth pressure
// 24: LE16 water temp
// 26: LE16 ??
// 28: LE16 ??
// 30: LE16 ??
// 32: LE16 ??
// 34: LE16 ??
static dc_status_t
deepblu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
{
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
const unsigned char *data = deepblu->base.data;
int len = deepblu->base.size;
if (len < 256)
return DC_STATUS_IO;
datetime->year = data[6] + (data[7] << 8);
datetime->day = data[8];
datetime->month = data[9];
datetime->minute = data[10];
datetime->hour = data[11];
datetime->second = 0;
datetime->timezone = DC_TIMEZONE_NONE;
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepblu_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
if (!value)
return DC_STATUS_INVALIDARGS;
/* This whole sequence should be standardized */
if (!(deepblu->cache.initialized & (1 << type)))
return DC_STATUS_UNSUPPORTED;
switch (type) {
case DC_FIELD_DIVETIME:
return DC_FIELD_VALUE(deepblu->cache, value, DIVETIME);
case DC_FIELD_MAXDEPTH:
return DC_FIELD_VALUE(deepblu->cache, value, MAXDEPTH);
case DC_FIELD_AVGDEPTH:
return DC_FIELD_VALUE(deepblu->cache, value, AVGDEPTH);
case DC_FIELD_GASMIX_COUNT:
case DC_FIELD_TANK_COUNT:
return DC_FIELD_VALUE(deepblu->cache, value, GASMIX_COUNT);
case DC_FIELD_GASMIX:
if (flags >= MAXGASES)
return DC_STATUS_UNSUPPORTED;
return DC_FIELD_INDEX(deepblu->cache, value, GASMIX, flags);
case DC_FIELD_SALINITY:
return DC_FIELD_VALUE(deepblu->cache, value, SALINITY);
case DC_FIELD_ATMOSPHERIC:
return DC_FIELD_VALUE(deepblu->cache, value, ATMOSPHERIC);
case DC_FIELD_DIVEMODE:
return DC_FIELD_VALUE(deepblu->cache, value, DIVEMODE);
case DC_FIELD_TANK:
return DC_STATUS_UNSUPPORTED;
case DC_FIELD_STRING:
return dc_field_get_string(&deepblu->cache, flags, (dc_field_string_t *)value);
default:
return DC_STATUS_UNSUPPORTED;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepblu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
const unsigned char *data = deepblu->base.data;
int len = deepblu->base.size, i;
deepblu->callback = callback;
deepblu->userdata = userdata;
// Skip the header information
if (len < 256)
return DC_STATUS_IO;
data += 256;
len -= 256;
// The rest should be samples every 20s with temperature and depth
for (i = 0; i < len/4; i++) {
dc_sample_value_t sample = {0};
unsigned int temp = data[0]+256*data[1];
unsigned int pressure = data[2]+256*data[3];
data += 4;
sample.time = (i+1)*deepblu->sample_interval;
if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
sample.depth = pressure_to_depth(pressure);
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
sample.temperature = temp / 10.0;
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
}
return DC_STATUS_SUCCESS;
}

View File

@ -49,6 +49,7 @@ static int dc_filter_garmin (dc_transport_t transport, const void *userdata);
static int dc_filter_mares (dc_transport_t transport, const void *userdata);
static int dc_filter_divesystem (dc_transport_t transport, const void *userdata);
static int dc_filter_oceanic (dc_transport_t transport, const void *userdata);
static int dc_filter_deepblu (dc_transport_t transport, const void *userdata);
static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item);
@ -379,6 +380,8 @@ static const dc_descriptor_t g_descriptors[] = {
{"Tecdiving", "DiveComputer.eu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_tecdiving},
/* Garmin */
{"Garmin", "Descent Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, dc_filter_garmin},
/* Deepblu */
{"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU, 0, DC_TRANSPORT_BLE, dc_filter_deepblu},
};
static int
@ -645,6 +648,19 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata)
return 1;
}
static int dc_filter_deepblu (dc_transport_t transport, const void *userdata)
{
static const char * const bluetooth[] = {
"COSMIQ",
};
if (transport == DC_TRANSPORT_BLE) {
return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name);
}
return 1;
}
dc_status_t
dc_descriptor_iterator (dc_iterator_t **out)
{

View File

@ -58,6 +58,7 @@
#include "cochran_commander.h"
#include "tecdiving_divecomputereu.h"
#include "garmin.h"
#include "deepblu.h"
#include "device-private.h"
#include "context-private.h"
@ -215,6 +216,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
case DC_FAMILY_GARMIN:
rc = garmin_device_open (&device, context, iostream);
break;
case DC_FAMILY_DEEPBLU:
rc = deepblu_device_open (&device, context, iostream);
break;
default:
return DC_STATUS_INVALIDARGS;
}

View File

@ -58,6 +58,7 @@
#include "cochran_commander.h"
#include "tecdiving_divecomputereu.h"
#include "garmin.h"
#include "deepblu.h"
#include "context-private.h"
#include "parser-private.h"
@ -176,6 +177,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
case DC_FAMILY_GARMIN:
rc = garmin_parser_create (&parser, context);
break;
case DC_FAMILY_DEEPBLU:
rc = deepblu_parser_create (&parser, context);
break;
default:
return DC_STATUS_INVALIDARGS;
}