diff --git a/examples/common.c b/examples/common.c index d5740d8..ebc1542 100644 --- a/examples/common.c +++ b/examples/common.c @@ -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[] = { diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h index 4ba705b..0819843 100644 --- a/include/libdivecomputer/common.h +++ b/include/libdivecomputer/common.h @@ -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 diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index b7dc2e8..0ae0ed6 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -506,6 +506,14 @@ RelativePath="..\src\garmin_parser.c" > + + + + @@ -856,6 +864,10 @@ RelativePath="..\src\garmin.h" > + + diff --git a/src/Makefile.am b/src/Makefile.am index a6ca28d..af2bc6d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/deepblu.c b/src/deepblu.c new file mode 100644 index 0000000..12054a8 --- /dev/null +++ b/src/deepblu.c @@ -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 +#include + +#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; +} diff --git a/src/deepblu.h b/src/deepblu.h new file mode 100644 index 0000000..92b821f --- /dev/null +++ b/src/deepblu.h @@ -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 +#include +#include +#include + +#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 */ diff --git a/src/deepblu_parser.c b/src/deepblu_parser.c new file mode 100644 index 0000000..073710e --- /dev/null +++ b/src/deepblu_parser.c @@ -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 +#include +#include +#include + +#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; +} diff --git a/src/descriptor.c b/src/descriptor.c index 17c3283..7a19cd7 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -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) { diff --git a/src/device.c b/src/device.c index ce9a29f..15bfcf3 100644 --- a/src/device.c +++ b/src/device.c @@ -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; } diff --git a/src/parser.c b/src/parser.c index ca05a5e..fa37f33 100644 --- a/src/parser.c +++ b/src/parser.c @@ -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; }