Deepblu Cosmiq+: add basic dive information parsing

This at least approximates downloading a dive from the Deepblu Cosmiq+,
and gives reasonable profiles for my test-dives (in a dive computer
chamber, not real dives).

I'm sure there's a lot to be improved here, and this literally only gets
depth and water temperature, but that seems to be what the Cosmiq+ gives
us.

Lots of credit to Michael Werle for bluetooth packet captures, and some basic analysis of the protocol.

Packet-logging-by: Michael Werle <mwerle@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Linus Torvalds 2019-08-17 15:06:16 -07:00
parent 2efa83eeee
commit eacfe4011a
2 changed files with 99 additions and 5 deletions

View File

@ -340,6 +340,9 @@ deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[
{ {
deepblu_device_t *device = (deepblu_device_t *)abstract; deepblu_device_t *device = (deepblu_device_t *)abstract;
ERROR (device->base.context, "Deepblu Cosmiq+ set_fingerprint called");
HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "set_fingerprint", data, size);
if (size && size != sizeof (device->fingerprint)) if (size && size != sizeof (device->fingerprint))
return DC_STATUS_INVALIDARGS; return DC_STATUS_INVALIDARGS;
@ -348,7 +351,6 @@ deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[
else else
memset (device->fingerprint, 0, sizeof (device->fingerprint)); memset (device->fingerprint, 0, sizeof (device->fingerprint));
ERROR (device->base.context, "Deepblu Cosmiq+ set_fingerprint called");
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
@ -410,24 +412,28 @@ deepblu_download_dive(deepblu_device_t *device, unsigned char nr, dc_dive_callba
status = deepblu_recv_bulk(device, RSP_DIVESTAT, header, header_len); status = deepblu_recv_bulk(device, RSP_DIVESTAT, header, header_len);
if (status != DC_STATUS_SUCCESS) if (status != DC_STATUS_SUCCESS)
return status; return status;
memset(header + header_len, 0, 256 - header_len);
status = deepblu_send_recv(device, CMD_GETPROFILE, &nr, 1, profilebytes, sizeof(profilebytes)); status = deepblu_send_recv(device, CMD_GETPROFILE, &nr, 1, profilebytes, sizeof(profilebytes));
if (status != DC_STATUS_SUCCESS) if (status != DC_STATUS_SUCCESS)
return status; return status;
profile_len = (profilebytes[0] << 8) | profilebytes[1]; profile_len = (profilebytes[0] << 8) | profilebytes[1];
profile = malloc(profile_len); profile = malloc(256 + profile_len);
if (!profile) { if (!profile) {
ERROR (device->base.context, "Insufficient buffer space available."); ERROR (device->base.context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY; return DC_STATUS_NOMEMORY;
} }
status = deepblu_recv_bulk(device, RSP_DIVEPROF, profile, profile_len); // 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) if (status != DC_STATUS_SUCCESS)
return status; return status;
if (callback) if (callback)
callback(profile, profile_len, header, header_len, userdata); callback(profile, profile_len+256, header, header_len, userdata);
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }

View File

@ -144,26 +144,89 @@ static void add_string_fmt(deepblu_parser_t *deepblu, const char *desc, const ch
add_string(deepblu, desc, buffer); add_string(deepblu, desc, buffer);
} }
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 static dc_status_t
deepblu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) deepblu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
{ {
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract; deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
const unsigned char *hdr = data;
const unsigned char *profile = data + 256;
unsigned int divetime, maxpressure;
if (size < 256)
return DC_STATUS_IO;
divetime = hdr[12] + 256*hdr[13]; // Divetime in minutes
maxpressure = hdr[22] + 256*hdr[23]; // Maxpressure in millibar
deepblu->callback = NULL; deepblu->callback = NULL;
deepblu->userdata = NULL; deepblu->userdata = NULL;
memset(&deepblu->cache, 0, sizeof(deepblu->cache)); memset(&deepblu->cache, 0, sizeof(deepblu->cache));
ASSIGN_FIELD(DIVETIME, 60*divetime);
ASSIGN_FIELD(MAXDEPTH, pressure_to_depth(maxpressure));
ERROR (abstract->context, "Deepblu Cosmiq+ parser_set_data() called"); ERROR (abstract->context, "Deepblu Cosmiq+ parser_set_data() called");
return DC_STATUS_SUCCESS; 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 static dc_status_t
deepblu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) deepblu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
{ {
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract; 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;
HEXDUMP(abstract->context, DC_LOGLEVEL_DEBUG, "get_datetile", data, len);
ERROR (abstract->context, "Deepblu Cosmiq+ parser_get_datetime() called"); ERROR (abstract->context, "Deepblu Cosmiq+ parser_get_datetime() called");
return DC_STATUS_UNSUPPORTED; return DC_STATUS_SUCCESS;
} }
static dc_status_t get_string_field(dc_field_string_t *strings, unsigned idx, dc_field_string_t *value) static dc_status_t get_string_field(dc_field_string_t *strings, unsigned idx, dc_field_string_t *value)
@ -231,10 +294,35 @@ static dc_status_t
deepblu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) deepblu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{ {
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract; 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->callback = callback;
deepblu->userdata = userdata; 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*20;
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);
}
ERROR (abstract->context, "Deepblu Cosmiq+ samples_foreach() called"); ERROR (abstract->context, "Deepblu Cosmiq+ samples_foreach() called");
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }