Somewhat basic support, but the data the Deepblu Cosmiq+ reports is pretty basic. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
279 lines
7.8 KiB
C
279 lines
7.8 KiB
C
/*
|
|
* 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;
|
|
}
|