/* * 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; }