diff options
Diffstat (limited to 'core/import-seac.c')
-rw-r--r-- | core/import-seac.c | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/core/import-seac.c b/core/import-seac.c new file mode 100644 index 000000000..4b34cbe99 --- /dev/null +++ b/core/import-seac.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifdef __clang__ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#endif + +#include "qthelper.h" +#include "ssrf.h" +#include "dive.h" +#include "subsurface-string.h" +#include "subsurface-time.h" +#include "parse.h" +#include "divelist.h" +#include "device.h" +#include "membuffer.h" +#include "gettext.h" +#include "tag.h" +#include "errorhelper.h" +#include <string.h> + +/* Process gas change event for seac database. + * Create gas change event at the time of the + * current sample. + */ +static int seac_gaschange(void *param, sqlite3_stmt *sqlstmt) +{ + struct parser_state *state = (struct parser_state *)param; + + event_start(state); + state->cur_event.time.seconds = sqlite3_column_int(sqlstmt, 1); + strcpy(state->cur_event.name, "gaschange"); + state->cur_event.gas.mix.o2.permille = 10 * sqlite3_column_int(sqlstmt, 4); + event_end(state); + + return 0; +} + +/* Callback function to parse seac dives. Reads headers_dive table to read dive + * information into divecomputer struct. + */ +static int seac_dive(void *param, int columns, char **data, char **column) +{ + UNUSED(columns); + UNUSED(column); + int retval = 0, cylnum = 0; + int year, month, day, hour, min, sec, tz; + char isodatetime[30]; + time_t divetime; + struct gasmix lastgas, curgas; + struct parser_state *state = (struct parser_state *)param; + sqlite3 *handle = state->sql_handle; + sqlite3_stmt *sqlstmt; + + const char *get_samples = "SELECT dive_number, runtime_s, depth_cm, temperature_mCx10, active_O2_fr, first_stop_depth_cm, first_stop_time_s, ndl_tts_s, cns, gf_l, gf_h FROM dive_data WHERE dive_number = ? ORDER BY runtime_s ASC"; + /* 0 = dive_number + * 1 = runtime_s + * 2 = depth_cm + * 3 = temperature_mCx10 - eg dC + * 4 = active_O2_fr + * 5 = first_stop_depth_cm + * 6 = first_stop_time_s + * 7 = ndl_tts_s + * 8 = cns + * 9 = gf-l + * 10 = gf-h + */ + + dive_start(state); + state->cur_dive->number = atoi(data[0]); + + // Create first cylinder + cylinder_t *curcyl = get_or_create_cylinder(state->cur_dive, 0); + + // Get time and date + sscanf(data[2], "%d/%d/%2d", &day, &month, &year); + sscanf(data[4], "%2d:%2d:%2d", &hour, &min, &sec); + year += 2000; + + tz = atoi(data[3]); + // Timezone offset lookup array + const char timezoneoffset[][7] = + {"+12:00", // 0 + "+11:00", // 1 + "+10:00", // 2 + "+09:30", // 3 + "+09:00", // 4 + "+08:00", // 5 + "+07:00", // 6 + "+06:00", // 7 + "+05:00", // 8 + "+04:30", // 9 + "+04:00", // 10 + "+03:30", // 11 + "+03:00", // 12 + "+02:00", // 13 + "+01:00", // 14 + "+00:00", // 15 + "-01:00", // 16 + "-02:00", // 17 + "-03:00", // 18 + "-03:30", // 19 + "-04:00", // 20 + "-04:30", // 21 + "-05:00", // 22 + "-05:30", // 23 + "-05:45", // 24 + "-06:00", // 25 + "-06:30", // 26 + "-07:00", // 27 + "-08:00", // 28 + "-08:45", // 29 + "-09:00", // 30 + "-09:30", // 31 + "-09:45", // 32 + "-10:00", // 33 + "-10:30", // 34 + "-11:00", // 35 + "-11:30", // 36 + "-12:00", // 37 + "-12:45", // 38 + "-13:00", // 39 + "-13:45", // 40 + "-14:00"}; // 41 + + sprintf(isodatetime, "%4i-%02i-%02iT%02i:%02i:%02i%6s", year, month, day, hour, min, sec, timezoneoffset[tz]); + divetime = get_dive_datetime_from_isostring(isodatetime); + state->cur_dive->when = divetime; + + // 6 = dive_type + // Dive type 2? + if (data[6]) { + switch (atoi(data[6])) { + case 1: + state->cur_dive->dc.divemode = OC; + break; + // Gauge Mode + case 2: + state->cur_dive->dc.divemode = UNDEF_COMP_TYPE; + break; + case 3: + state->cur_dive->dc.divemode = FREEDIVE; + break; + default: + if (verbose) { + fprintf(stderr, "Unknown divetype %i", atoi(data[6])); + } + } + } + + // 9 = comments from seac app + if (data[9]) { + utf8_string(data[9], &state->cur_dive->notes); + } + + // 10 = dive duration + if (data[10]) { + state->cur_dive->dc.duration.seconds = atoi(data[10]); + } + + // 8 = water_type + /* TODO: Seac only offers fresh / salt, and doesn't + * seem to record correctly currently. I have both + * fresh and saltwater dives and water type is reported + * as 150 for both. + */ + if (data[8]) { + switch (atoi(data[8])) { + case 150: + state->cur_dive->salinity = 0; + break; + case 100: + state->cur_dive->salinity = 1; + break; + default: + if (verbose) { + fprintf(stderr, "Unknown salinity %i", atoi(data[8])); + } + } + } + + + if (data[11]) { + state->cur_dive->dc.maxdepth.mm = 10 * atoi(data[11]); + } + + // Create sql_stmt type to query DB + retval = sqlite3_prepare_v2(handle, get_samples, -1, &sqlstmt, 0); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Preparing SQL object failed when getting SeacSync dives.\n"); + return 1; + } + + // Bind current dive number to sql statement + sqlite3_bind_int(sqlstmt, 1, state->cur_dive->number); + + // Catch a bad query + retval = sqlite3_step(sqlstmt); + if (retval == SQLITE_ERROR) { + fprintf(stderr, "%s", "Getting dive data from SeacSync DB failed.\n"); + return 1; + } + + settings_start(state); + dc_settings_start(state); + + utf8_string(data[1], &state->cur_dive->dc.serial); + utf8_string(data[12], &state->cur_dive->dc.fw_version); + state->cur_dive->dc.model = strdup("Seac Action"); + // TODO: Calculate device hash from string + state->cur_dive->dc.deviceid = 0xffffffff; + add_extra_data(&state->cur_dive->dc, "GF-Lo", (const char*)sqlite3_column_text(sqlstmt, 9)); + add_extra_data(&state->cur_dive->dc, "GF-Hi", (const char*)sqlite3_column_text(sqlstmt, 10)); + + dc_settings_end(state); + settings_end(state); + + if (data[11]) { + state->cur_dive->dc.maxdepth.mm = 10 * atoi(data[11]); + } + + curcyl->gasmix.o2.permille = 10 * sqlite3_column_int(sqlstmt, 4); + + + // Track gasses to tell when switch occurs + lastgas = curcyl->gasmix; + curgas = curcyl->gasmix; + + // Read samples + while (retval == SQLITE_ROW) { + sample_start(state); + state->cur_sample->time.seconds = sqlite3_column_int(sqlstmt, 1); + state->cur_sample->depth.mm = 10 * sqlite3_column_int(sqlstmt, 2); + state->cur_sample->temperature.mkelvin = cC_to_mkelvin(sqlite3_column_int(sqlstmt, 3)); + curgas.o2.permille = 10 * sqlite3_column_int(sqlstmt, 4); + if (!same_gasmix(lastgas, curgas)) { + seac_gaschange(state, sqlstmt); + lastgas = curgas; + cylnum ^= 1; // Only need to toggle between two cylinders + curcyl = get_or_create_cylinder(state->cur_dive, cylnum); + curcyl->gasmix.o2.permille = 10 * sqlite3_column_int(sqlstmt, 4); + } + state->cur_sample->stopdepth.mm = 10 * sqlite3_column_int(sqlstmt, 5); + state->cur_sample->stoptime.seconds = sqlite3_column_int(sqlstmt, 6); + state->cur_sample->ndl.seconds = sqlite3_column_int(sqlstmt, 7); + state->cur_sample->cns = sqlite3_column_int(sqlstmt, 8); + sample_end(state); + retval = sqlite3_step(sqlstmt); + } + + sqlite3_finalize(sqlstmt); + dive_end(state); + + return SQLITE_OK; +} + +/** Read SeacSync divesDB.db sqlite3 database into dive and samples. + * + * Each row returned in the query of headers_dive creates a new dive. + * The callback function performs another SQL query on the other + * table, to read in the sample values. + */ +int parse_seac_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, + struct dive_table *table, struct trip_table *trips, struct dive_site_table *sites) +{ + UNUSED(buffer); + UNUSED(size); + + int retval; + char *err = NULL; + struct parser_state state; + + init_parser_state(&state); + state.target_table = table; + state.trips = trips; + state.sites = sites; + state.sql_handle = handle; + + const char *get_dives = "SELECT dive_number, device_sn, date, timezone, time, elapsed_surface_time, dive_type, start_mode, water_type, comment, total_dive_time, max_depth, firmware_version FROM headers_dive"; + /* 0 = dive_number + * 1 = device_sn + * 2 = date + * 3 = timezone + * 4 = time + * 5 = elapsed_surface_time + * 6 = dive_type + * 7 = start_mode + * 8 = water_type + * 9 = comment + * 10 = total_dive_time + * 11 = max_depth + * 12 = firmware version + */ + + retval = sqlite3_exec(handle, get_dives, &seac_dive, &state, &err); + free_parser_state(&state); + + if (retval != SQLITE_OK) { + fprintf(stderr, "Database query failed '%s'.\n", url); + return 1; + } + + return 0; +}
\ No newline at end of file |