From 0e196310f9cda2ccca27e9cf96186639b5658249 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 25 Oct 2020 13:28:55 +0100 Subject: [PATCH] cleanup: split out divecomputer functions from dive.c Since dive.c is so huge, split out divecomputer-related functions into divecomputer.[c|h], sample.[c|h] and extradata.[c|h]. This does not give huge compile time improvements, since struct dive contains a struct divecomputer and therefore dive.h has to include divecomputer.h. However, it make things distinctly more clear. Signed-off-by: Berthold Stoeger --- backend-shared/exportfuncs.cpp | 1 + core/CMakeLists.txt | 6 + core/cochran.c | 1 + core/device.cpp | 184 -------- core/device.h | 1 - core/dive.c | 388 +--------------- core/dive.h | 102 +---- core/divecomputer.c | 543 +++++++++++++++++++++++ core/divecomputer.h | 79 ++++ core/divelist.c | 1 + core/extradata.h | 11 + core/import-cobalt.c | 4 +- core/import-csv.c | 3 +- core/import-divinglog.c | 2 + core/import-seac.c | 2 +- core/import-shearwater.c | 1 + core/import-suunto.c | 3 +- core/libdivecomputer.c | 1 + core/liquivision.c | 1 + core/load-git.c | 3 +- core/ostctools.c | 1 + core/parse-xml.c | 2 + core/parse.c | 4 +- core/parse.h | 6 +- core/planner.c | 1 + core/profile.c | 1 + core/sample.c | 45 ++ core/sample.h | 41 ++ core/save-git.c | 2 + core/save-html.c | 1 + core/save-xml.c | 2 + core/statistics.c | 1 + core/uemis.c | 1 + packaging/ios/Subsurface-mobile.pro | 5 + profile-widget/diveeventitem.cpp | 1 + qt-models/divecomputerextradatamodel.cpp | 4 +- qt-models/diveplannermodel.cpp | 1 + tests/testparse.cpp | 1 + 38 files changed, 777 insertions(+), 680 deletions(-) create mode 100644 core/divecomputer.c create mode 100644 core/divecomputer.h create mode 100644 core/extradata.h create mode 100644 core/sample.c create mode 100644 core/sample.h diff --git a/backend-shared/exportfuncs.cpp b/backend-shared/exportfuncs.cpp index a8ba7ead4..469e078a4 100644 --- a/backend-shared/exportfuncs.cpp +++ b/backend-shared/exportfuncs.cpp @@ -13,6 +13,7 @@ #include "core/divefilter.h" #include "core/divesite.h" #include "core/picture.h" +#include "core/sample.h" #include "exportfuncs.h" #if !defined(SUBSURFACE_MOBILE) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 08fca0864..276185fff 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -64,6 +64,9 @@ set(SUBSURFACE_CORE_LIB_SRCS display.h dive.c dive.h + divecomputer.c + divecomputer.h + dive.h divefilter.cpp divefilter.h divelist.c @@ -84,6 +87,7 @@ set(SUBSURFACE_CORE_LIB_SRCS errorhelper.c exif.cpp exif.h + extradata.h file.c file.h filterconstraint.cpp @@ -144,6 +148,8 @@ set(SUBSURFACE_CORE_LIB_SRCS qt-init.cpp qthelper.cpp qthelper.h + sample.c + sample.h save-git.c save-html.c save-html.h diff --git a/core/cochran.c b/core/cochran.c index 547d053b5..4451ff8a2 100644 --- a/core/cochran.c +++ b/core/cochran.c @@ -15,6 +15,7 @@ #include "dive.h" #include "file.h" +#include "sample.h" #include "subsurface-time.h" #include "units.h" #include "sha1.h" diff --git a/core/device.cpp b/core/device.cpp index cdc2d1963..8c78fe43c 100644 --- a/core/device.cpp +++ b/core/device.cpp @@ -8,190 +8,6 @@ #include "core/settings/qPrefDiveComputer.h" #include // for QString::number -/* - * Good fake dive profiles are hard. - * - * "depthtime" is the integral of the dive depth over - * time ("area" of the dive profile). We want that - * area to match the average depth (avg_d*max_t). - * - * To do that, we generate a 6-point profile: - * - * (0, 0) - * (t1, max_d) - * (t2, max_d) - * (t3, d) - * (t4, d) - * (max_t, 0) - * - * with the same ascent/descent rates between the - * different depths. - * - * NOTE: avg_d, max_d and max_t are given constants. - * The rest we can/should play around with to get a - * good-looking profile. - * - * That six-point profile gives a total area of: - * - * (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) - * - * And the "same ascent/descent rates" requirement - * gives us (time per depth must be same): - * - * t1 / max_d = (t3-t2) / (max_d-d) - * t1 / max_d = (max_t-t4) / d - * - * We also obviously require: - * - * 0 <= t1 <= t2 <= t3 <= t4 <= max_t - * - * Let us call 'd_frac = d / max_d', and we get: - * - * Total area must match average depth-time: - * - * (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) = avg_d*max_t - * max_d*(max_t-t1-(1-d_frac)*(t4-t3)) = avg_d*max_t - * max_t-t1-(1-d_frac)*(t4-t3) = avg_d*max_t/max_d - * t1+(1-d_frac)*(t4-t3) = max_t*(1-avg_d/max_d) - * - * and descent slope must match ascent slopes: - * - * t1 / max_d = (t3-t2) / (max_d*(1-d_frac)) - * t1 = (t3-t2)/(1-d_frac) - * - * and - * - * t1 / max_d = (max_t-t4) / (max_d*d_frac) - * t1 = (max_t-t4)/d_frac - * - * In general, we have more free variables than we have constraints, - * but we can aim for certain basics, like a good ascent slope. - */ -static int fill_samples(struct sample *s, int max_d, int avg_d, int max_t, double slope, double d_frac) -{ - double t_frac = max_t * (1 - avg_d / (double)max_d); - int t1 = lrint(max_d / slope); - int t4 = lrint(max_t - t1 * d_frac); - int t3 = lrint(t4 - (t_frac - t1) / (1 - d_frac)); - int t2 = lrint(t3 - t1 * (1 - d_frac)); - - if (t1 < 0 || t1 > t2 || t2 > t3 || t3 > t4 || t4 > max_t) - return 0; - - s[1].time.seconds = t1; - s[1].depth.mm = max_d; - s[2].time.seconds = t2; - s[2].depth.mm = max_d; - s[3].time.seconds = t3; - s[3].depth.mm = lrint(max_d * d_frac); - s[4].time.seconds = t4; - s[4].depth.mm = lrint(max_d * d_frac); - - return 1; -} - -/* we have no average depth; instead of making up a random average depth - * we should assume either a PADI rectangular profile (for short and/or - * shallow dives) or more reasonably a six point profile with a 3 minute - * safety stop at 5m */ -static void fill_samples_no_avg(struct sample *s, int max_d, int max_t, double slope) -{ - // shallow or short dives are just trapecoids based on the given slope - if (max_d < 10000 || max_t < 600) { - s[1].time.seconds = lrint(max_d / slope); - s[1].depth.mm = max_d; - s[2].time.seconds = max_t - lrint(max_d / slope); - s[2].depth.mm = max_d; - } else { - s[1].time.seconds = lrint(max_d / slope); - s[1].depth.mm = max_d; - s[2].time.seconds = max_t - lrint(max_d / slope) - 180; - s[2].depth.mm = max_d; - s[3].time.seconds = max_t - lrint(5000 / slope) - 180; - s[3].depth.mm = 5000; - s[4].time.seconds = max_t - lrint(5000 / slope); - s[4].depth.mm = 5000; - } -} - -extern "C" void fake_dc(struct divecomputer *dc) -{ - alloc_samples(dc, 6); - struct sample *fake = dc->sample; - int i; - - dc->samples = 6; - - /* The dive has no samples, so create a few fake ones */ - int max_t = dc->duration.seconds; - int max_d = dc->maxdepth.mm; - int avg_d = dc->meandepth.mm; - - memset(fake, 0, 6 * sizeof(struct sample)); - fake[5].time.seconds = max_t; - for (i = 0; i < 6; i++) { - fake[i].bearing.degrees = -1; - fake[i].ndl.seconds = -1; - } - if (!max_t || !max_d) { - dc->samples = 0; - return; - } - - /* Set last manually entered time to the total dive length */ - dc->last_manual_time = dc->duration; - - /* - * We want to fake the profile so that the average - * depth ends up correct. However, in the absence of - * a reasonable average, let's just make something - * up. Note that 'avg_d == max_d' is _not_ a reasonable - * average. - * We explicitly treat avg_d == 0 differently */ - if (avg_d == 0) { - /* we try for a sane slope, but bow to the insanity of - * the user supplied data */ - fill_samples_no_avg(fake, max_d, max_t, MAX(2.0 * max_d / max_t, (double)prefs.ascratelast6m)); - if (fake[3].time.seconds == 0) { // just a 4 point profile - dc->samples = 4; - fake[3].time.seconds = max_t; - } - return; - } - if (avg_d < max_d / 10 || avg_d >= max_d) { - avg_d = (max_d + 10000) / 3; - if (avg_d > max_d) - avg_d = max_d * 2 / 3; - } - if (!avg_d) - avg_d = 1; - - /* - * Ok, first we try a basic profile with a specific ascent - * rate (5 meters per minute) and d_frac (1/3). - */ - if (fill_samples(fake, max_d, avg_d, max_t, (double)prefs.ascratelast6m, 0.33)) - return; - - /* - * Ok, assume that didn't work because we cannot make the - * average come out right because it was a quick deep dive - * followed by a much shallower region - */ - if (fill_samples(fake, max_d, avg_d, max_t, 10000.0 / 60, 0.10)) - return; - - /* - * Uhhuh. That didn't work. We'd need to find a good combination that - * satisfies our constraints. Currently, we don't, we just give insane - * slopes. - */ - if (fill_samples(fake, max_d, avg_d, max_t, 10000.0, 0.01)) - return; - - /* Even that didn't work? Give up, there's something wrong */ -} - struct device_table device_table; bool device::operator==(const device &a) const diff --git a/core/device.h b/core/device.h index 21e264d39..1de436e33 100644 --- a/core/device.h +++ b/core/device.h @@ -16,7 +16,6 @@ struct dive_table; // global device table extern struct device_table device_table; -extern void fake_dc(struct divecomputer *dc); extern void set_dc_deviceid(struct divecomputer *dc, unsigned int deviceid, const struct device_table *table); extern void add_devices_of_dive(const struct dive *dive, struct device_table *table); diff --git a/core/dive.c b/core/dive.c index 0aa7a4324..1fe0d44f2 100644 --- a/core/dive.c +++ b/core/dive.c @@ -14,9 +14,11 @@ #include "divesite.h" #include "errorhelper.h" #include "event.h" +#include "extradata.h" #include "qthelper.h" #include "membuffer.h" #include "picture.h" +#include "sample.h" #include "tag.h" #include "trip.h" #include "structured_list.h" @@ -40,47 +42,6 @@ const char *divemode_text[] = {"OC", "CCR", "PSCR", "Freedive"}; static int calculate_depth_to_mbar(int depth, pressure_t surface_pressure, int salinity); -/* - * Adding a cylinder pressure sample field is not quite as trivial as it - * perhaps should be. - * - * We try to keep the same sensor index for the same sensor, so that even - * if the dive computer doesn't give pressure information for every sample, - * we don't move pressure information around between the different sensor - * indices. - * - * The "prepare_sample()" function will always copy the sensor indices - * from the previous sample, so the indices are pre-populated (but the - * pressures obviously are not) - */ -void add_sample_pressure(struct sample *sample, int sensor, int mbar) -{ - int idx; - - if (!mbar) - return; - - /* Do we already have a slot for this sensor */ - for (idx = 0; idx < MAX_SENSORS; idx++) { - if (sensor != sample->sensor[idx]) - continue; - sample->pressure[idx].mbar = mbar; - return; - } - - /* Pick the first unused index if we couldn't reuse one */ - for (idx = 0; idx < MAX_SENSORS; idx++) { - if (sample->pressure[idx].mbar) - continue; - sample->sensor[idx] = sensor; - sample->pressure[idx].mbar = mbar; - return; - } - - /* We do not have enough slots for the pressure samples. */ - /* Should we warn the user about dropping pressure data? */ -} - /* * The legacy format for sample pressures has a single pressure * for each sample that can have any sensor, plus a possible @@ -143,32 +104,6 @@ struct event *create_gas_switch_event(struct dive *dive, struct divecomputer *dc return ev; } -void add_event_to_dc(struct divecomputer *dc, struct event *ev) -{ - struct event **p; - - p = &dc->events; - - /* insert in the sorted list of events */ - while (*p && (*p)->time.seconds <= ev->time.seconds) - p = &(*p)->next; - ev->next = *p; - *p = ev; -} - -struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name) -{ - struct event *ev = create_event(time, type, flags, value, name); - - if (!ev) - return NULL; - - add_event_to_dc(dc, ev); - - remember_event(name); - return ev; -} - void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx) { /* sanity check so we don't crash */ @@ -183,31 +118,6 @@ void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int second add_event_to_dc(dc, ev); } -/* Substitutes an event in a divecomputer for another. No reordering is performed! */ -void swap_event(struct divecomputer *dc, struct event *from, struct event *to) -{ - for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) { - if (*ep == from) { - to->next = from->next; - *ep = to; - from->next = NULL; // For good measure. - break; - } - } -} - -/* Remove given event from dive computer. Does *not* free the event. */ -void remove_event_from_dc(struct divecomputer *dc, struct event *event) -{ - for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) { - if (*ep == event) { - *ep = event->next; - event->next = NULL; // For good measure. - break; - } - } -} - /* since the name is an array as part of the structure (how silly is that?) we * have to actually remove the existing event and replace it with a new one. * WARNING, WARNING... this may end up freeing event in case that event is indeed @@ -232,43 +142,6 @@ void update_event_name(struct dive *d, struct event *event, const char *name) invalidate_dive_cache(d); } -void add_extra_data(struct divecomputer *dc, const char *key, const char *value) -{ - struct extra_data **ed = &dc->extra_data; - - while (*ed) - ed = &(*ed)->next; - *ed = malloc(sizeof(struct extra_data)); - if (*ed) { - (*ed)->key = strdup(key); - (*ed)->value = strdup(value); - (*ed)->next = NULL; - } -} - -/* Find the divemode at time 'time' (in seconds) into the dive. Sequentially step through the divemode-change events, - * saving the dive mode for each event. When the events occur AFTER 'time' seconds, the last stored divemode - * is returned. This function is self-tracking, relying on setting the event pointer 'evp' so that, in each iteration - * that calls this function, the search does not have to begin at the first event of the dive */ -enum divemode_t get_current_divemode(const struct divecomputer *dc, int time, const struct event **evp, enum divemode_t *divemode) -{ - const struct event *ev = *evp; - if (dc) { - if (*divemode == UNDEF_COMP_TYPE) { - *divemode = dc->divemode; - ev = get_next_event(dc->events, "modechange"); - } - } else { - ev = NULL; - } - while (ev && ev->time.seconds < time) { - *divemode = (enum divemode_t) ev->value; - ev = get_next_event(ev->next, "modechange"); - } - *evp = ev; - return *divemode; -} - struct gasmix get_gasmix_from_event(const struct dive *dive, const struct event *ev) { if (ev && event_is_gaschange(ev)) { @@ -304,9 +177,6 @@ struct dive *alloc_dive(void) return dive; } -static void free_dc(struct divecomputer *dc); -static void free_dc_contents(struct divecomputer *dc); - /* copy an element in a list of dive computer extra data */ static void copy_extra_data(struct extra_data *sed, struct extra_data *ded) { @@ -346,14 +216,6 @@ static void copy_dc_renumber(struct dive *d, const struct divecomputer *sdc, str ddc->next = NULL; } -/* The first divecomputer is embedded in the dive structure. Free its data but not - * the structure itself. For all remainding dcs in the list, free data *and* structures. */ -void free_dive_dcs(struct divecomputer *dc) -{ - free_dc_contents(dc); - STRUCTURED_LIST_FREE(struct divecomputer, dc->next, free_dc); -} - static void free_dive_structures(struct dive *d) { if (!d) @@ -476,24 +338,6 @@ void selective_copy_dive(const struct dive *s, struct dive *d, struct dive_compo } #undef CONDITIONAL_COPY_STRING -/* copies all events in this dive computer */ -void copy_events(const struct divecomputer *s, struct divecomputer *d) -{ - const struct event *ev; - struct event **pev; - if (!s || !d) - return; - ev = s->events; - pev = &d->events; - while (ev != NULL) { - struct event *new_ev = clone_event(ev); - *pev = new_ev; - pev = &new_ev->next; - ev = ev->next; - } - *pev = NULL; -} - /* copies all events from all dive computers before a given time this is used when editing a dive in the planner to preserve the events of the old dive */ @@ -542,81 +386,6 @@ void copy_used_cylinders(const struct dive *s, struct dive *d, bool used_only) } } -void copy_samples(const struct divecomputer *s, struct divecomputer *d) -{ - /* instead of carefully copying them one by one and calling add_sample - * over and over again, let's just copy the whole blob */ - if (!s || !d) - return; - int nr = s->samples; - d->samples = nr; - d->alloc_samples = nr; - // We expect to be able to read the memory in the other end of the pointer - // if its a valid pointer, so don't expect malloc() to return NULL for - // zero-sized malloc, do it ourselves. - d->sample = NULL; - - if(!nr) - return; - - d->sample = malloc(nr * sizeof(struct sample)); - if (d->sample) - memcpy(d->sample, s->sample, nr * sizeof(struct sample)); -} - -/* make room for num samples; if not enough space is available, the sample - * array is reallocated and the existing samples are copied. */ -void alloc_samples(struct divecomputer *dc, int num) -{ - if (num > dc->alloc_samples) { - dc->alloc_samples = (num * 3) / 2 + 10; - dc->sample = realloc(dc->sample, dc->alloc_samples * sizeof(struct sample)); - if (!dc->sample) - dc->samples = dc->alloc_samples = 0; - } -} - -void free_samples(struct divecomputer *dc) -{ - if (dc) { - free(dc->sample); - dc->sample = 0; - dc->samples = 0; - dc->alloc_samples = 0; - } -} - -struct sample *prepare_sample(struct divecomputer *dc) -{ - if (dc) { - int nr = dc->samples; - struct sample *sample; - alloc_samples(dc, nr + 1); - if (!dc->sample) - return NULL; - sample = dc->sample + nr; - memset(sample, 0, sizeof(*sample)); - - // Copy the sensor numbers - but not the pressure values - // from the previous sample if any. - if (nr) { - for (int idx = 0; idx < MAX_SENSORS; idx++) - sample->sensor[idx] = sample[-1].sensor[idx]; - } - // Init some values with -1 - sample->bearing.degrees = -1; - sample->ndl.seconds = -1; - - return sample; - } - return NULL; -} - -void finish_sample(struct divecomputer *dc) -{ - dc->samples++; -} - /* * So when we re-calculate maxdepth and meandepth, we will * not override the old numbers if they are close to the @@ -653,40 +422,6 @@ static void update_temperature(temperature_t *temperature, int new) } } -/* - * Calculate how long we were actually under water, and the average - * depth while under water. - * - * This ignores any surface time in the middle of the dive. - */ -void fixup_dc_duration(struct divecomputer *dc) -{ - int duration, i; - int lasttime, lastdepth, depthtime; - - duration = 0; - lasttime = 0; - lastdepth = 0; - depthtime = 0; - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int time = sample->time.seconds; - int depth = sample->depth.mm; - - /* We ignore segments at the surface */ - if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) { - duration += time - lasttime; - depthtime += (time - lasttime) * (depth + lastdepth) / 2; - } - lastdepth = depth; - lasttime = time; - } - if (duration) { - dc->duration.seconds = duration; - dc->meandepth.mm = (depthtime + duration / 2) / duration; - } -} - /* Which cylinders had gas used? */ #define SOME_GAS 5000 static bool cylinder_used(const cylinder_t *cyl) @@ -1129,49 +864,12 @@ static void fixup_duration(struct dive *dive) dive->duration.seconds = duration.seconds; } -/* - * What do the dive computers say the water temperature is? - * (not in the samples, but as dc property for dcs that support that) - */ -unsigned int dc_watertemp(const struct divecomputer *dc) -{ - int sum = 0, nr = 0; - - do { - if (dc->watertemp.mkelvin) { - sum += dc->watertemp.mkelvin; - nr++; - } - } while ((dc = dc->next) != NULL); - if (!nr) - return 0; - return (sum + nr / 2) / nr; -} - static void fixup_watertemp(struct dive *dive) { if (!dive->watertemp.mkelvin) dive->watertemp.mkelvin = dc_watertemp(&dive->dc); } -/* - * What do the dive computers say the air temperature is? - */ -unsigned int dc_airtemp(const struct divecomputer *dc) -{ - int sum = 0, nr = 0; - - do { - if (dc->airtemp.mkelvin) { - sum += dc->airtemp.mkelvin; - nr++; - } - } while ((dc = dc->next) != NULL); - if (!nr) - return 0; - return (sum + nr / 2) / nr; -} - static void fixup_airtemp(struct dive *dive) { if (!dive->airtemp.mkelvin) @@ -1583,18 +1281,6 @@ struct dive *fixup_dive(struct dive *dive) #define MERGE_TXT(res, a, b, n, sep) res->n = merge_text(a->n, b->n, sep) #define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n -struct sample *add_sample(const struct sample *sample, int time, struct divecomputer *dc) -{ - struct sample *p = prepare_sample(dc); - - if (p) { - *p = *sample; - p->time.seconds = time; - finish_sample(dc); - } - return p; -} - /* * This is like add_sample(), but if the distance from the last sample * is excessive, we add two surface samples in between. @@ -2537,35 +2223,6 @@ static int similar(unsigned long a, unsigned long b, unsigned long expected) return 0; } -/* - * Match two dive computer entries against each other, and - * tell if it's the same dive. Return 0 if "don't know", - * positive for "same dive" and negative for "definitely - * not the same dive" - */ -int match_one_dc(const struct divecomputer *a, const struct divecomputer *b) -{ - /* Not same model? Don't know if matching.. */ - if (!a->model || !b->model) - return 0; - if (strcasecmp(a->model, b->model)) - return 0; - - /* Different device ID's? Don't know */ - if (a->deviceid != b->deviceid) - return 0; - - /* Do we have dive IDs? */ - if (!a->diveid || !b->diveid) - return 0; - - /* - * If they have different dive ID's on the same - * dive computer, that's a definite "same or not" - */ - return a->diveid == b->diveid && a->when == b->when ? 1 : -1; -} - /* * Match every dive computer against each other to see if * we have a matching dive. @@ -2680,28 +2337,6 @@ struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded return res; } -static void free_extra_data(struct extra_data *ed) -{ - free((void *)ed->key); - free((void *)ed->value); -} - -static void free_dc_contents(struct divecomputer *dc) -{ - free(dc->sample); - free((void *)dc->model); - free((void *)dc->serial); - free((void *)dc->fw_version); - free_events(dc->events); - STRUCTURED_LIST_FREE(struct extra_data, dc->extra_data, free_extra_data); -} - -static void free_dc(struct divecomputer *dc) -{ - free_dc_contents(dc); - free(dc); -} - static int same_sample(struct sample *a, struct sample *b) { if (a->time.seconds != b->time.seconds) @@ -2886,11 +2521,6 @@ static void join_dive_computers(struct dive *d, struct divecomputer *res, remove_redundant_dc(res, prefer_downloaded); } -bool is_dc_planner(const struct divecomputer *dc) -{ - return same_string(dc->model, "planned dive"); -} - // Does this dive have a dive computer for which is_dc_planner has value planned bool has_planned(const struct dive *dive, bool planned) { @@ -3486,20 +3116,6 @@ void split_divecomputer(const struct dive *src, int num, struct dive **out1, str } } -/* helper function to make it easier to work with our structures - * we don't interpolate here, just use the value from the last sample up to that time */ -int get_depth_at_time(const struct divecomputer *dc, unsigned int time) -{ - int depth = 0; - if (dc && dc->sample) - for (int i = 0; i < dc->samples; i++) { - if (dc->sample[i].time.seconds > time) - break; - depth = dc->sample[i].depth.mm; - } - return depth; -} - //Calculate O2 in best mix fraction_t best_o2(depth_t depth, const struct dive *dive) { diff --git a/core/dive.h b/core/dive.h index 968c221ec..c596061d9 100644 --- a/core/dive.h +++ b/core/dive.h @@ -13,6 +13,7 @@ #include #include "divemode.h" +#include "divecomputer.h" #include "equipment.h" #include "picture.h" @@ -20,8 +21,6 @@ extern "C" { #endif -struct event; - extern int last_xml_version; extern const char *cylinderuse_text[NUM_GAS_USE]; @@ -39,73 +38,13 @@ static inline int interpolate(int a, int b, int part, int whole) return (a+b)/2; } -#define MAX_SENSORS 2 -struct sample // BASE TYPE BYTES UNITS RANGE DESCRIPTION -{ // --------- ----- ----- ----- ----------- - duration_t time; // int32_t 4 seconds (0-34 yrs) elapsed dive time up to this sample - duration_t stoptime; // int32_t 4 seconds (0-34 yrs) time duration of next deco stop - duration_t ndl; // int32_t 4 seconds (-1 no val, 0-34 yrs) time duration before no-deco limit - duration_t tts; // int32_t 4 seconds (0-34 yrs) time duration to reach the surface - duration_t rbt; // int32_t 4 seconds (0-34 yrs) remaining bottom time - depth_t depth; // int32_t 4 mm (0-2000 km) dive depth of this sample - depth_t stopdepth; // int32_t 4 mm (0-2000 km) depth of next deco stop - temperature_t temperature; // uint32_t 4 mK (0-4 MK) ambient temperature - pressure_t pressure[MAX_SENSORS]; // int32_t 4 mbar (0-2 Mbar) cylinder pressures (main and CCR o2) - o2pressure_t setpoint; // uint16_t 2 mbar (0-65 bar) O2 partial pressure (will be setpoint) - o2pressure_t o2sensor[3]; // uint16_t 6 mbar (0-65 bar) Up to 3 PO2 sensor values (rebreather) - bearing_t bearing; // int16_t 2 degrees (-1 no val, 0-360 deg) compass bearing - uint8_t sensor[MAX_SENSORS]; // uint8_t 1 sensorID (0-255) ID of cylinder pressure sensor - uint16_t cns; // uint16_t 1 % (0-64k %) cns% accumulated - uint8_t heartbeat; // uint8_t 1 beats/m (0-255) heart rate measurement - volume_t sac; // 4 ml/min predefined SAC - bool in_deco; // bool 1 y/n y/n this sample is part of deco - bool manually_entered; // bool 1 y/n y/n this sample was entered by the user, - // not calculated when planning a dive -}; // Total size of structure: 57 bytes, excluding padding at end - -struct extra_data { - const char *key; - const char *value; - struct extra_data *next; -}; - -/* - * NOTE! The deviceid and diveid are model-specific *hashes* of - * whatever device identification that model may have. Different - * dive computers will have different identifying data, it could - * be a firmware number or a serial ID (in either string or in - * numeric format), and we do not care. - * - * The only thing we care about is that subsurface will hash - * that information the same way. So then you can check the ID - * of a dive computer by comparing the hashes for equality. - * - * A deviceid or diveid of zero is assumed to be "no ID". - */ -struct divecomputer { - timestamp_t when; - duration_t duration, surfacetime, last_manual_time; - depth_t maxdepth, meandepth; - temperature_t airtemp, watertemp; - pressure_t surface_pressure; - enum divemode_t divemode; // dive computer type: OC(default) or CCR - uint8_t no_o2sensors; // rebreathers: number of O2 sensors used - int salinity; // kg per 10000 l - const char *model, *serial, *fw_version; - uint32_t deviceid, diveid; - int samples, alloc_samples; - struct sample *sample; - struct event *events; - struct extra_data *extra_data; - struct divecomputer *next; -}; - struct dive_site; struct dive_site_table; struct dive_table; struct dive_trip; -struct trip_table; struct full_text_cache; +struct event; +struct trip_table; struct dive { struct dive_trip *divetrip; timestamp_t when; @@ -171,13 +110,8 @@ struct dive_components { unsigned int weights : 1; }; -extern enum divemode_t get_current_divemode(const struct divecomputer *dc, int time, const struct event **evp, enum divemode_t *divemode); -extern struct event *get_next_divemodechange(const struct event **evd, bool update_pointer); -extern enum divemode_t get_divemode_at_time(const struct divecomputer *dc, int dtime, const struct event **ev_dmc); - extern bool has_gaschange_event(const struct dive *dive, const struct divecomputer *dc, int idx); extern int explicit_first_cylinder(const struct dive *dive, const struct divecomputer *dc); -extern int get_depth_at_time(const struct divecomputer *dc, unsigned int time); extern fraction_t best_o2(depth_t depth, const struct dive *dive); extern fraction_t best_he(depth_t depth, const struct dive *dive, bool o2narcotic, fraction_t fo2); @@ -191,8 +125,6 @@ extern int mbar_to_depth(int mbar, const struct dive *dive); extern depth_t gas_mod(struct gasmix mix, pressure_t po2_limit, const struct dive *dive, int roundto); extern depth_t gas_mnd(struct gasmix mix, depth_t end, const struct dive *dive, int roundto); -#define SURFACE_THRESHOLD 750 /* somewhat arbitrary: only below 75cm is it really diving */ - extern bool autogroup; struct dive *unregister_dive(int idx); @@ -243,9 +175,6 @@ extern location_t dive_get_gps_location(const struct dive *d); extern bool time_during_dive_with_offset(const struct dive *dive, timestamp_t when, timestamp_t offset); -/* Check if two dive computer entries are the exact same dive (-1=no/0=maybe/1=yes) */ -extern int match_one_dc(const struct divecomputer *a, const struct divecomputer *b); - extern int save_dives(const char *filename); extern int save_dives_logic(const char *filename, bool select_only, bool anonymize); extern int save_dive(FILE *f, struct dive *dive, bool anonymize); @@ -262,19 +191,12 @@ extern bool subsurface_user_is_root(void); extern struct dive *alloc_dive(void); extern void free_dive(struct dive *); -extern void free_dive_dcs(struct divecomputer *dc); extern void record_dive_to_table(struct dive *dive, struct dive_table *table); extern void clear_dive(struct dive *dive); extern void copy_dive(const struct dive *s, struct dive *d); extern void selective_copy_dive(const struct dive *s, struct dive *d, struct dive_components what, bool clear); extern struct dive *move_dive(struct dive *s); -extern void alloc_samples(struct divecomputer *dc, int num); -extern void free_samples(struct divecomputer *dc); -extern struct sample *prepare_sample(struct divecomputer *dc); -extern void finish_sample(struct divecomputer *dc); -extern struct sample *add_sample(const struct sample *sample, int time, struct divecomputer *dc); -extern void add_sample_pressure(struct sample *sample, int sensor, int mbar); extern int legacy_format_o2pressures(const struct dive *dive, const struct divecomputer *dc); extern bool dive_less_than(const struct dive *a, const struct dive *b); @@ -284,28 +206,18 @@ extern struct dive *fixup_dive(struct dive *dive); extern pressure_t calculate_surface_pressure(const struct dive *dive); extern pressure_t un_fixup_surface_pressure(const struct dive *d); extern int get_dive_salinity(const struct dive *dive); -extern void fixup_dc_duration(struct divecomputer *dc); extern int dive_getUniqID(); -extern unsigned int dc_airtemp(const struct divecomputer *dc); -extern unsigned int dc_watertemp(const struct divecomputer *dc); extern int split_dive(const struct dive *dive, struct dive **new1, struct dive **new2); extern int split_dive_at_time(const struct dive *dive, duration_t time, struct dive **new1, struct dive **new2); extern struct dive *merge_dives(const struct dive *a, const struct dive *b, int offset, bool prefer_downloaded, struct dive_trip **trip, struct dive_site **site); extern struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded); -extern void copy_events(const struct divecomputer *s, struct divecomputer *d); extern void copy_events_until(const struct dive *sd, struct dive *dd, int time); extern void copy_used_cylinders(const struct dive *s, struct dive *d, bool used_only); -extern void copy_samples(const struct divecomputer *s, struct divecomputer *d); extern bool is_cylinder_used(const struct dive *dive, int idx); extern bool is_cylinder_prot(const struct dive *dive, int idx); extern void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int time, int idx); extern struct event *create_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx); -extern void add_event_to_dc(struct divecomputer *dc, struct event *ev); -extern void swap_event(struct divecomputer *dc, struct event *from, struct event *to); -extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name); -extern void remove_event_from_dc(struct divecomputer *dc, struct event *event); extern void update_event_name(struct dive *d, struct event *event, const char *name); -extern void add_extra_data(struct divecomputer *dc, const char *key, const char *value); extern void per_cylinder_mean_depth(const struct dive *dive, struct divecomputer *dc, int *mean, int *duration); extern int get_cylinder_index(const struct dive *dive, const struct event *ev); extern struct gasmix get_gasmix_from_event(const struct dive *, const struct event *ev); @@ -328,7 +240,6 @@ extern const char *existing_filename; extern void subsurface_command_line_init(int *, char ***); extern void subsurface_command_line_exit(int *, char ***); -extern bool is_dc_planner(const struct divecomputer *dc); extern bool has_planned(const struct dive *dive, bool planned); /* Get gasmixes at increasing timestamps. @@ -340,13 +251,6 @@ extern struct gasmix get_gasmix(const struct dive *dive, const struct divecomput /* Get gasmix at a given time */ extern struct gasmix get_gasmix_at_time(const struct dive *dive, const struct divecomputer *dc, duration_t time); -/* these structs holds the information that - * describes the cylinders / weight systems. - * they are global variables initialized in equipment.c - * used to fill the combobox in the add/edit cylinder - * dialog - */ - extern void set_informational_units(const char *units); extern void set_git_prefs(const char *prefs); diff --git a/core/divecomputer.c b/core/divecomputer.c new file mode 100644 index 000000000..fa836f7ca --- /dev/null +++ b/core/divecomputer.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "divecomputer.h" +#include "event.h" +#include "extradata.h" +#include "pref.h" +#include "sample.h" +#include "structured_list.h" +#include "subsurface-string.h" + +#include +#include + +/* + * Good fake dive profiles are hard. + * + * "depthtime" is the integral of the dive depth over + * time ("area" of the dive profile). We want that + * area to match the average depth (avg_d*max_t). + * + * To do that, we generate a 6-point profile: + * + * (0, 0) + * (t1, max_d) + * (t2, max_d) + * (t3, d) + * (t4, d) + * (max_t, 0) + * + * with the same ascent/descent rates between the + * different depths. + * + * NOTE: avg_d, max_d and max_t are given constants. + * The rest we can/should play around with to get a + * good-looking profile. + * + * That six-point profile gives a total area of: + * + * (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) + * + * And the "same ascent/descent rates" requirement + * gives us (time per depth must be same): + * + * t1 / max_d = (t3-t2) / (max_d-d) + * t1 / max_d = (max_t-t4) / d + * + * We also obviously require: + * + * 0 <= t1 <= t2 <= t3 <= t4 <= max_t + * + * Let us call 'd_frac = d / max_d', and we get: + * + * Total area must match average depth-time: + * + * (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) = avg_d*max_t + * max_d*(max_t-t1-(1-d_frac)*(t4-t3)) = avg_d*max_t + * max_t-t1-(1-d_frac)*(t4-t3) = avg_d*max_t/max_d + * t1+(1-d_frac)*(t4-t3) = max_t*(1-avg_d/max_d) + * + * and descent slope must match ascent slopes: + * + * t1 / max_d = (t3-t2) / (max_d*(1-d_frac)) + * t1 = (t3-t2)/(1-d_frac) + * + * and + * + * t1 / max_d = (max_t-t4) / (max_d*d_frac) + * t1 = (max_t-t4)/d_frac + * + * In general, we have more free variables than we have constraints, + * but we can aim for certain basics, like a good ascent slope. + */ +static int fill_samples(struct sample *s, int max_d, int avg_d, int max_t, double slope, double d_frac) +{ + double t_frac = max_t * (1 - avg_d / (double)max_d); + int t1 = lrint(max_d / slope); + int t4 = lrint(max_t - t1 * d_frac); + int t3 = lrint(t4 - (t_frac - t1) / (1 - d_frac)); + int t2 = lrint(t3 - t1 * (1 - d_frac)); + + if (t1 < 0 || t1 > t2 || t2 > t3 || t3 > t4 || t4 > max_t) + return 0; + + s[1].time.seconds = t1; + s[1].depth.mm = max_d; + s[2].time.seconds = t2; + s[2].depth.mm = max_d; + s[3].time.seconds = t3; + s[3].depth.mm = lrint(max_d * d_frac); + s[4].time.seconds = t4; + s[4].depth.mm = lrint(max_d * d_frac); + + return 1; +} + +/* we have no average depth; instead of making up a random average depth + * we should assume either a PADI rectangular profile (for short and/or + * shallow dives) or more reasonably a six point profile with a 3 minute + * safety stop at 5m */ +static void fill_samples_no_avg(struct sample *s, int max_d, int max_t, double slope) +{ + // shallow or short dives are just trapecoids based on the given slope + if (max_d < 10000 || max_t < 600) { + s[1].time.seconds = lrint(max_d / slope); + s[1].depth.mm = max_d; + s[2].time.seconds = max_t - lrint(max_d / slope); + s[2].depth.mm = max_d; + } else { + s[1].time.seconds = lrint(max_d / slope); + s[1].depth.mm = max_d; + s[2].time.seconds = max_t - lrint(max_d / slope) - 180; + s[2].depth.mm = max_d; + s[3].time.seconds = max_t - lrint(5000 / slope) - 180; + s[3].depth.mm = 5000; + s[4].time.seconds = max_t - lrint(5000 / slope); + s[4].depth.mm = 5000; + } +} + +void fake_dc(struct divecomputer *dc) +{ + alloc_samples(dc, 6); + struct sample *fake = dc->sample; + int i; + + dc->samples = 6; + + /* The dive has no samples, so create a few fake ones */ + int max_t = dc->duration.seconds; + int max_d = dc->maxdepth.mm; + int avg_d = dc->meandepth.mm; + + memset(fake, 0, 6 * sizeof(struct sample)); + fake[5].time.seconds = max_t; + for (i = 0; i < 6; i++) { + fake[i].bearing.degrees = -1; + fake[i].ndl.seconds = -1; + } + if (!max_t || !max_d) { + dc->samples = 0; + return; + } + + /* Set last manually entered time to the total dive length */ + dc->last_manual_time = dc->duration; + + /* + * We want to fake the profile so that the average + * depth ends up correct. However, in the absence of + * a reasonable average, let's just make something + * up. Note that 'avg_d == max_d' is _not_ a reasonable + * average. + * We explicitly treat avg_d == 0 differently */ + if (avg_d == 0) { + /* we try for a sane slope, but bow to the insanity of + * the user supplied data */ + fill_samples_no_avg(fake, max_d, max_t, MAX(2.0 * max_d / max_t, (double)prefs.ascratelast6m)); + if (fake[3].time.seconds == 0) { // just a 4 point profile + dc->samples = 4; + fake[3].time.seconds = max_t; + } + return; + } + if (avg_d < max_d / 10 || avg_d >= max_d) { + avg_d = (max_d + 10000) / 3; + if (avg_d > max_d) + avg_d = max_d * 2 / 3; + } + if (!avg_d) + avg_d = 1; + + /* + * Ok, first we try a basic profile with a specific ascent + * rate (5 meters per minute) and d_frac (1/3). + */ + if (fill_samples(fake, max_d, avg_d, max_t, (double)prefs.ascratelast6m, 0.33)) + return; + + /* + * Ok, assume that didn't work because we cannot make the + * average come out right because it was a quick deep dive + * followed by a much shallower region + */ + if (fill_samples(fake, max_d, avg_d, max_t, 10000.0 / 60, 0.10)) + return; + + /* + * Uhhuh. That didn't work. We'd need to find a good combination that + * satisfies our constraints. Currently, we don't, we just give insane + * slopes. + */ + if (fill_samples(fake, max_d, avg_d, max_t, 10000.0, 0.01)) + return; + + /* Even that didn't work? Give up, there's something wrong */ +} + +/* Find the divemode at time 'time' (in seconds) into the dive. Sequentially step through the divemode-change events, + * saving the dive mode for each event. When the events occur AFTER 'time' seconds, the last stored divemode + * is returned. This function is self-tracking, relying on setting the event pointer 'evp' so that, in each iteration + * that calls this function, the search does not have to begin at the first event of the dive */ +enum divemode_t get_current_divemode(const struct divecomputer *dc, int time, const struct event **evp, enum divemode_t *divemode) +{ + const struct event *ev = *evp; + if (dc) { + if (*divemode == UNDEF_COMP_TYPE) { + *divemode = dc->divemode; + ev = get_next_event(dc->events, "modechange"); + } + } else { + ev = NULL; + } + while (ev && ev->time.seconds < time) { + *divemode = (enum divemode_t) ev->value; + ev = get_next_event(ev->next, "modechange"); + } + *evp = ev; + return *divemode; +} + + +/* helper function to make it easier to work with our structures + * we don't interpolate here, just use the value from the last sample up to that time */ +int get_depth_at_time(const struct divecomputer *dc, unsigned int time) +{ + int depth = 0; + if (dc && dc->sample) + for (int i = 0; i < dc->samples; i++) { + if (dc->sample[i].time.seconds > time) + break; + depth = dc->sample[i].depth.mm; + } + return depth; +} + + +/* The first divecomputer is embedded in the dive structure. Free its data but not + * the structure itself. For all remainding dcs in the list, free data *and* structures. */ +void free_dive_dcs(struct divecomputer *dc) +{ + free_dc_contents(dc); + STRUCTURED_LIST_FREE(struct divecomputer, dc->next, free_dc); +} + +/* make room for num samples; if not enough space is available, the sample + * array is reallocated and the existing samples are copied. */ +void alloc_samples(struct divecomputer *dc, int num) +{ + if (num > dc->alloc_samples) { + dc->alloc_samples = (num * 3) / 2 + 10; + dc->sample = realloc(dc->sample, dc->alloc_samples * sizeof(struct sample)); + if (!dc->sample) + dc->samples = dc->alloc_samples = 0; + } +} + +void free_samples(struct divecomputer *dc) +{ + if (dc) { + free(dc->sample); + dc->sample = 0; + dc->samples = 0; + dc->alloc_samples = 0; + } +} + +struct sample *prepare_sample(struct divecomputer *dc) +{ + if (dc) { + int nr = dc->samples; + struct sample *sample; + alloc_samples(dc, nr + 1); + if (!dc->sample) + return NULL; + sample = dc->sample + nr; + memset(sample, 0, sizeof(*sample)); + + // Copy the sensor numbers - but not the pressure values + // from the previous sample if any. + if (nr) { + for (int idx = 0; idx < MAX_SENSORS; idx++) + sample->sensor[idx] = sample[-1].sensor[idx]; + } + // Init some values with -1 + sample->bearing.degrees = -1; + sample->ndl.seconds = -1; + + return sample; + } + return NULL; +} + + +void finish_sample(struct divecomputer *dc) +{ + dc->samples++; +} + +struct sample *add_sample(const struct sample *sample, int time, struct divecomputer *dc) +{ + struct sample *p = prepare_sample(dc); + + if (p) { + *p = *sample; + p->time.seconds = time; + finish_sample(dc); + } + return p; +} + +/* + * Calculate how long we were actually under water, and the average + * depth while under water. + * + * This ignores any surface time in the middle of the dive. + */ +void fixup_dc_duration(struct divecomputer *dc) +{ + int duration, i; + int lasttime, lastdepth, depthtime; + + duration = 0; + lasttime = 0; + lastdepth = 0; + depthtime = 0; + for (i = 0; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + int time = sample->time.seconds; + int depth = sample->depth.mm; + + /* We ignore segments at the surface */ + if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) { + duration += time - lasttime; + depthtime += (time - lasttime) * (depth + lastdepth) / 2; + } + lastdepth = depth; + lasttime = time; + } + if (duration) { + dc->duration.seconds = duration; + dc->meandepth.mm = (depthtime + duration / 2) / duration; + } +} + + +/* + * What do the dive computers say the water temperature is? + * (not in the samples, but as dc property for dcs that support that) + */ +unsigned int dc_watertemp(const struct divecomputer *dc) +{ + int sum = 0, nr = 0; + + do { + if (dc->watertemp.mkelvin) { + sum += dc->watertemp.mkelvin; + nr++; + } + } while ((dc = dc->next) != NULL); + if (!nr) + return 0; + return (sum + nr / 2) / nr; +} + +/* + * What do the dive computers say the air temperature is? + */ +unsigned int dc_airtemp(const struct divecomputer *dc) +{ + int sum = 0, nr = 0; + + do { + if (dc->airtemp.mkelvin) { + sum += dc->airtemp.mkelvin; + nr++; + } + } while ((dc = dc->next) != NULL); + if (!nr) + return 0; + return (sum + nr / 2) / nr; +} + +/* copies all events in this dive computer */ +void copy_events(const struct divecomputer *s, struct divecomputer *d) +{ + const struct event *ev; + struct event **pev; + if (!s || !d) + return; + ev = s->events; + pev = &d->events; + while (ev != NULL) { + struct event *new_ev = clone_event(ev); + *pev = new_ev; + pev = &new_ev->next; + ev = ev->next; + } + *pev = NULL; +} + +void copy_samples(const struct divecomputer *s, struct divecomputer *d) +{ + /* instead of carefully copying them one by one and calling add_sample + * over and over again, let's just copy the whole blob */ + if (!s || !d) + return; + int nr = s->samples; + d->samples = nr; + d->alloc_samples = nr; + // We expect to be able to read the memory in the other end of the pointer + // if its a valid pointer, so don't expect malloc() to return NULL for + // zero-sized malloc, do it ourselves. + d->sample = NULL; + + if(!nr) + return; + + d->sample = malloc(nr * sizeof(struct sample)); + if (d->sample) + memcpy(d->sample, s->sample, nr * sizeof(struct sample)); +} + +void add_event_to_dc(struct divecomputer *dc, struct event *ev) +{ + struct event **p; + + p = &dc->events; + + /* insert in the sorted list of events */ + while (*p && (*p)->time.seconds <= ev->time.seconds) + p = &(*p)->next; + ev->next = *p; + *p = ev; +} + +struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name) +{ + struct event *ev = create_event(time, type, flags, value, name); + + if (!ev) + return NULL; + + add_event_to_dc(dc, ev); + + remember_event(name); + return ev; +} + +/* Substitutes an event in a divecomputer for another. No reordering is performed! */ +void swap_event(struct divecomputer *dc, struct event *from, struct event *to) +{ + for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) { + if (*ep == from) { + to->next = from->next; + *ep = to; + from->next = NULL; // For good measure. + break; + } + } +} + +/* Remove given event from dive computer. Does *not* free the event. */ +void remove_event_from_dc(struct divecomputer *dc, struct event *event) +{ + for (struct event **ep = &dc->events; *ep; ep = &(*ep)->next) { + if (*ep == event) { + *ep = event->next; + event->next = NULL; // For good measure. + break; + } + } +} + +void add_extra_data(struct divecomputer *dc, const char *key, const char *value) +{ + struct extra_data **ed = &dc->extra_data; + + while (*ed) + ed = &(*ed)->next; + *ed = malloc(sizeof(struct extra_data)); + if (*ed) { + (*ed)->key = strdup(key); + (*ed)->value = strdup(value); + (*ed)->next = NULL; + } +} + +bool is_dc_planner(const struct divecomputer *dc) +{ + return same_string(dc->model, "planned dive"); +} + +/* + * Match two dive computer entries against each other, and + * tell if it's the same dive. Return 0 if "don't know", + * positive for "same dive" and negative for "definitely + * not the same dive" + */ +int match_one_dc(const struct divecomputer *a, const struct divecomputer *b) +{ + /* Not same model? Don't know if matching.. */ + if (!a->model || !b->model) + return 0; + if (strcasecmp(a->model, b->model)) + return 0; + + /* Different device ID's? Don't know */ + if (a->deviceid != b->deviceid) + return 0; + + /* Do we have dive IDs? */ + if (!a->diveid || !b->diveid) + return 0; + + /* + * If they have different dive ID's on the same + * dive computer, that's a definite "same or not" + */ + return a->diveid == b->diveid && a->when == b->when ? 1 : -1; +} + +static void free_extra_data(struct extra_data *ed) +{ + free((void *)ed->key); + free((void *)ed->value); +} + +void free_dc_contents(struct divecomputer *dc) +{ + free(dc->sample); + free((void *)dc->model); + free((void *)dc->serial); + free((void *)dc->fw_version); + free_events(dc->events); + STRUCTURED_LIST_FREE(struct extra_data, dc->extra_data, free_extra_data); +} + +void free_dc(struct divecomputer *dc) +{ + free_dc_contents(dc); + free(dc); +} + diff --git a/core/divecomputer.h b/core/divecomputer.h new file mode 100644 index 000000000..eb48e9b86 --- /dev/null +++ b/core/divecomputer.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef DIVECOMPUTER_H +#define DIVECOMPUTER_H + +#include "divemode.h" +#include "units.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct extra_data; +struct sample; + +/* Is this header the correct place? */ +#define SURFACE_THRESHOLD 750 /* somewhat arbitrary: only below 75cm is it really diving */ + +/* + * NOTE! The deviceid and diveid are model-specific *hashes* of + * whatever device identification that model may have. Different + * dive computers will have different identifying data, it could + * be a firmware number or a serial ID (in either string or in + * numeric format), and we do not care. + * + * The only thing we care about is that subsurface will hash + * that information the same way. So then you can check the ID + * of a dive computer by comparing the hashes for equality. + * + * A deviceid or diveid of zero is assumed to be "no ID". + */ +struct divecomputer { + timestamp_t when; + duration_t duration, surfacetime, last_manual_time; + depth_t maxdepth, meandepth; + temperature_t airtemp, watertemp; + pressure_t surface_pressure; + enum divemode_t divemode; // dive computer type: OC(default) or CCR + uint8_t no_o2sensors; // rebreathers: number of O2 sensors used + int salinity; // kg per 10000 l + const char *model, *serial, *fw_version; + uint32_t deviceid, diveid; + int samples, alloc_samples; + struct sample *sample; + struct event *events; + struct extra_data *extra_data; + struct divecomputer *next; +}; + +extern void fake_dc(struct divecomputer *dc); +extern void free_dc(struct divecomputer *dc); +extern void free_dc_contents(struct divecomputer *dc); +extern enum divemode_t get_current_divemode(const struct divecomputer *dc, int time, const struct event **evp, enum divemode_t *divemode); +extern int get_depth_at_time(const struct divecomputer *dc, unsigned int time); +extern void free_dive_dcs(struct divecomputer *dc); +extern void alloc_samples(struct divecomputer *dc, int num); +extern void free_samples(struct divecomputer *dc); +extern struct sample *prepare_sample(struct divecomputer *dc); +extern void finish_sample(struct divecomputer *dc); +extern struct sample *add_sample(const struct sample *sample, int time, struct divecomputer *dc); +extern void fixup_dc_duration(struct divecomputer *dc); +extern unsigned int dc_airtemp(const struct divecomputer *dc); +extern unsigned int dc_watertemp(const struct divecomputer *dc); +extern void copy_events(const struct divecomputer *s, struct divecomputer *d); +extern void swap_event(struct divecomputer *dc, struct event *from, struct event *to); +extern void copy_samples(const struct divecomputer *s, struct divecomputer *d); +extern void add_event_to_dc(struct divecomputer *dc, struct event *ev); +extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name); +extern void remove_event_from_dc(struct divecomputer *dc, struct event *event); +extern void add_extra_data(struct divecomputer *dc, const char *key, const char *value); +extern bool is_dc_planner(const struct divecomputer *dc); + +/* Check if two dive computer entries are the exact same dive (-1=no/0=maybe/1=yes) */ +extern int match_one_dc(const struct divecomputer *a, const struct divecomputer *b); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/divelist.c b/core/divelist.c index 7f93bacf2..7cbb5535c 100644 --- a/core/divelist.c +++ b/core/divelist.c @@ -15,6 +15,7 @@ #include "gettext.h" #include "git-access.h" #include "selection.h" +#include "sample.h" #include "table.h" #include "trip.h" diff --git a/core/extradata.h b/core/extradata.h new file mode 100644 index 000000000..4f9a72f20 --- /dev/null +++ b/core/extradata.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef EXTRADATA_H +#define EXTRADATA_H + +struct extra_data { + const char *key; + const char *value; + struct extra_data *next; +}; + +#endif diff --git a/core/import-cobalt.c b/core/import-cobalt.c index 7feb325f1..1d00dea0f 100644 --- a/core/import-cobalt.c +++ b/core/import-cobalt.c @@ -5,10 +5,12 @@ #endif #include "ssrf.h" +#include "dive.h" #include "divesite.h" #include "gas.h" -#include "subsurface-string.h" #include "parse.h" +#include "sample.h" +#include "subsurface-string.h" #include "divelist.h" #include "device.h" #include "membuffer.h" diff --git a/core/import-csv.c b/core/import-csv.c index dda4b8b79..5bdcfd989 100644 --- a/core/import-csv.c +++ b/core/import-csv.c @@ -2,13 +2,14 @@ #include #include +#include "dive.h" #include "errorhelper.h" #include "ssrf.h" #include "subsurface-string.h" #include "divelist.h" #include "file.h" #include "parse.h" -#include "subsurface-time.h" +#include "sample.h" #include "divelist.h" #include "gettext.h" #include "import-csv.h" diff --git a/core/import-divinglog.c b/core/import-divinglog.c index 24ab5c548..8f396b872 100644 --- a/core/import-divinglog.c +++ b/core/import-divinglog.c @@ -5,7 +5,9 @@ #endif #include "ssrf.h" +#include "dive.h" #include "divesite.h" +#include "sample.h" #include "subsurface-string.h" #include "parse.h" #include "divelist.h" diff --git a/core/import-seac.c b/core/import-seac.c index 38c7978af..da4e278ae 100644 --- a/core/import-seac.c +++ b/core/import-seac.c @@ -7,8 +7,8 @@ #include "qthelper.h" #include "ssrf.h" #include "dive.h" +#include "sample.h" #include "subsurface-string.h" -#include "subsurface-time.h" #include "parse.h" #include "divelist.h" #include "device.h" diff --git a/core/import-shearwater.c b/core/import-shearwater.c index 4dd67de2b..ad3c97b57 100644 --- a/core/import-shearwater.c +++ b/core/import-shearwater.c @@ -6,6 +6,7 @@ #include "ssrf.h" #include "dive.h" +#include "sample.h" #include "subsurface-string.h" #include "parse.h" #include "divelist.h" diff --git a/core/import-suunto.c b/core/import-suunto.c index 27d5680be..63a9cc622 100644 --- a/core/import-suunto.c +++ b/core/import-suunto.c @@ -6,8 +6,9 @@ #include "ssrf.h" #include "dive.h" -#include "subsurface-string.h" #include "parse.h" +#include "sample.h" +#include "subsurface-string.h" #include "divelist.h" #include "device.h" #include "membuffer.h" diff --git a/core/libdivecomputer.c b/core/libdivecomputer.c index b7d6ab14e..0194aa7f2 100644 --- a/core/libdivecomputer.c +++ b/core/libdivecomputer.c @@ -14,6 +14,7 @@ #include #include "gettext.h" #include "divesite.h" +#include "sample.h" #include "subsurface-string.h" #include "device.h" #include "dive.h" diff --git a/core/liquivision.c b/core/liquivision.c index c15e88e7a..c460fc9b8 100644 --- a/core/liquivision.c +++ b/core/liquivision.c @@ -5,6 +5,7 @@ #include "divesite.h" #include "dive.h" #include "file.h" +#include "sample.h" #include "strndup.h" // Convert bytes into an INT diff --git a/core/load-git.c b/core/load-git.c index 839c5019b..94eae3275 100644 --- a/core/load-git.c +++ b/core/load-git.c @@ -18,8 +18,9 @@ #include "divesite.h" #include "event.h" #include "errorhelper.h" -#include "trip.h" +#include "sample.h" #include "subsurface-string.h" +#include "trip.h" #include "device.h" #include "membuffer.h" #include "git-access.h" diff --git a/core/ostctools.c b/core/ostctools.c index 7efad32d3..77aac8946 100644 --- a/core/ostctools.c +++ b/core/ostctools.c @@ -8,6 +8,7 @@ #include "subsurface-string.h" #include "gettext.h" #include "dive.h" +#include "extradata.h" #include "file.h" #include "libdivecomputer.h" diff --git a/core/parse-xml.c b/core/parse-xml.c index aab25ffaf..c6e5ea1be 100644 --- a/core/parse-xml.c +++ b/core/parse-xml.c @@ -21,6 +21,7 @@ #include "gettext.h" +#include "dive.h" #include "divesite.h" #include "errorhelper.h" #include "subsurface-string.h" @@ -31,6 +32,7 @@ #include "membuffer.h" #include "picture.h" #include "qthelper.h" +#include "sample.h" #include "tag.h" #include "xmlparams.h" diff --git a/core/parse.c b/core/parse.c index 8de69e499..d2be71e21 100644 --- a/core/parse.c +++ b/core/parse.c @@ -7,10 +7,12 @@ #include #include +#include "parse.h" +#include "dive.h" #include "divesite.h" #include "errorhelper.h" +#include "sample.h" #include "subsurface-string.h" -#include "parse.h" #include "picture.h" #include "trip.h" #include "device.h" diff --git a/core/parse.h b/core/parse.h index 224403585..a46663c50 100644 --- a/core/parse.h +++ b/core/parse.h @@ -5,11 +5,13 @@ #define MAX_EVENT_NAME 128 #include "event.h" -#include "picture.h" -#include "dive.h" // for "struct extra_data" +#include "equipment.h" // for cylinder_t +#include "extradata.h" #include "filterpreset.h" +#include "picture.h" #include +#include struct xml_params; diff --git a/core/planner.c b/core/planner.c index f03b87f9a..bfadf4b7f 100644 --- a/core/planner.c +++ b/core/planner.c @@ -12,6 +12,7 @@ #include "ssrf.h" #include "dive.h" #include "divelist.h" // for init_decompression() +#include "sample.h" #include "subsurface-string.h" #include "deco.h" #include "errorhelper.h" diff --git a/core/profile.c b/core/profile.c index 0e7db76d2..05aae3b64 100644 --- a/core/profile.c +++ b/core/profile.c @@ -12,6 +12,7 @@ #include "display.h" #include "divelist.h" #include "event.h" +#include "sample.h" #include "subsurface-string.h" #include "profile.h" diff --git a/core/sample.c b/core/sample.c new file mode 100644 index 000000000..fd68a4aa7 --- /dev/null +++ b/core/sample.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "sample.h" + +/* + * Adding a cylinder pressure sample field is not quite as trivial as it + * perhaps should be. + * + * We try to keep the same sensor index for the same sensor, so that even + * if the dive computer doesn't give pressure information for every sample, + * we don't move pressure information around between the different sensor + * indices. + * + * The "prepare_sample()" function will always copy the sensor indices + * from the previous sample, so the indices are pre-populated (but the + * pressures obviously are not) + */ +void add_sample_pressure(struct sample *sample, int sensor, int mbar) +{ + int idx; + + if (!mbar) + return; + + /* Do we already have a slot for this sensor */ + for (idx = 0; idx < MAX_SENSORS; idx++) { + if (sensor != sample->sensor[idx]) + continue; + sample->pressure[idx].mbar = mbar; + return; + } + + /* Pick the first unused index if we couldn't reuse one */ + for (idx = 0; idx < MAX_SENSORS; idx++) { + if (sample->pressure[idx].mbar) + continue; + sample->sensor[idx] = sensor; + sample->pressure[idx].mbar = mbar; + return; + } + + /* We do not have enough slots for the pressure samples. */ + /* Should we warn the user about dropping pressure data? */ +} + diff --git a/core/sample.h b/core/sample.h new file mode 100644 index 000000000..75c7bb1b4 --- /dev/null +++ b/core/sample.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef SAMPLE_H +#define SAMPLE_H + +#include "units.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_SENSORS 2 +struct sample // BASE TYPE BYTES UNITS RANGE DESCRIPTION +{ // --------- ----- ----- ----- ----------- + duration_t time; // int32_t 4 seconds (0-34 yrs) elapsed dive time up to this sample + duration_t stoptime; // int32_t 4 seconds (0-34 yrs) time duration of next deco stop + duration_t ndl; // int32_t 4 seconds (-1 no val, 0-34 yrs) time duration before no-deco limit + duration_t tts; // int32_t 4 seconds (0-34 yrs) time duration to reach the surface + duration_t rbt; // int32_t 4 seconds (0-34 yrs) remaining bottom time + depth_t depth; // int32_t 4 mm (0-2000 km) dive depth of this sample + depth_t stopdepth; // int32_t 4 mm (0-2000 km) depth of next deco stop + temperature_t temperature; // uint32_t 4 mK (0-4 MK) ambient temperature + pressure_t pressure[MAX_SENSORS]; // int32_t 4 mbar (0-2 Mbar) cylinder pressures (main and CCR o2) + o2pressure_t setpoint; // uint16_t 2 mbar (0-65 bar) O2 partial pressure (will be setpoint) + o2pressure_t o2sensor[3]; // uint16_t 6 mbar (0-65 bar) Up to 3 PO2 sensor values (rebreather) + bearing_t bearing; // int16_t 2 degrees (-1 no val, 0-360 deg) compass bearing + uint8_t sensor[MAX_SENSORS]; // uint8_t 1 sensorID (0-255) ID of cylinder pressure sensor + uint16_t cns; // uint16_t 1 % (0-64k %) cns% accumulated + uint8_t heartbeat; // uint8_t 1 beats/m (0-255) heart rate measurement + volume_t sac; // 4 ml/min predefined SAC + bool in_deco; // bool 1 y/n y/n this sample is part of deco + bool manually_entered; // bool 1 y/n y/n this sample was entered by the user, + // not calculated when planning a dive +}; // Total size of structure: 57 bytes, excluding padding at end + +extern void add_sample_pressure(struct sample *sample, int sensor, int mbar); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/save-git.c b/core/save-git.c index 4fabe35cf..e875d2fb4 100644 --- a/core/save-git.c +++ b/core/save-git.c @@ -20,11 +20,13 @@ #include "divesite.h" #include "filterconstraint.h" #include "filterpreset.h" +#include "sample.h" #include "subsurface-string.h" #include "trip.h" #include "device.h" #include "errorhelper.h" #include "event.h" +#include "extradata.h" #include "membuffer.h" #include "git-access.h" #include "version.h" diff --git a/core/save-html.c b/core/save-html.c index 65b21a723..90aeb9d41 100644 --- a/core/save-html.c +++ b/core/save-html.c @@ -13,6 +13,7 @@ #include "event.h" #include "file.h" #include "picture.h" +#include "sample.h" #include "tag.h" #include "subsurface-time.h" #include "trip.h" diff --git a/core/save-xml.c b/core/save-xml.c index 13d038c22..9d5b8f6f8 100644 --- a/core/save-xml.c +++ b/core/save-xml.c @@ -15,8 +15,10 @@ #include "dive.h" #include "divesite.h" #include "errorhelper.h" +#include "extradata.h" #include "filterconstraint.h" #include "filterpreset.h" +#include "sample.h" #include "subsurface-string.h" #include "subsurface-time.h" #include "trip.h" diff --git a/core/statistics.c b/core/statistics.c index d22b6755c..86d1cfce6 100644 --- a/core/statistics.c +++ b/core/statistics.c @@ -13,6 +13,7 @@ #include "dive.h" #include "display.h" #include "event.h" +#include "sample.h" #include "subsurface-time.h" #include "trip.h" #include "statistics.h" diff --git a/core/uemis.c b/core/uemis.c index 7272e2897..a11c1625b 100644 --- a/core/uemis.c +++ b/core/uemis.c @@ -14,6 +14,7 @@ #include "uemis.h" #include "divesite.h" +#include "sample.h" #include #include diff --git a/packaging/ios/Subsurface-mobile.pro b/packaging/ios/Subsurface-mobile.pro index 605bce4cd..0c922e3b6 100644 --- a/packaging/ios/Subsurface-mobile.pro +++ b/packaging/ios/Subsurface-mobile.pro @@ -55,6 +55,7 @@ SOURCES += ../../subsurface-mobile-main.cpp \ ../../core/profile.c \ ../../core/device.cpp \ ../../core/dive.c \ + ../../core/divecomputer.c \ ../../core/divefilter.cpp \ ../../core/event.c \ ../../core/filterconstraint.cpp \ @@ -69,6 +70,7 @@ SOURCES += ../../subsurface-mobile-main.cpp \ ../../core/parse.c \ ../../core/picture.c \ ../../core/pictureobj.cpp \ + ../../core/sample.c \ ../../core/import-suunto.c \ ../../core/import-shearwater.c \ ../../core/import-seac.c \ @@ -205,7 +207,9 @@ HEADERS += \ ../../core/device.h \ ../../core/devicedetails.h \ ../../core/dive.h \ + ../../core/divecomputer.h \ ../../core/event.h \ + ../../core/extradata.h \ ../../core/git-access.h \ ../../core/gpslocation.h \ ../../core/imagedownloader.h \ @@ -242,6 +246,7 @@ HEADERS += \ ../../core/membuffer.h \ ../../core/metrics.h \ ../../core/qt-gui.h \ + ../../core/sample.h \ ../../core/selection.h \ ../../core/sha1.h \ ../../core/strndup.h \ diff --git a/profile-widget/diveeventitem.cpp b/profile-widget/diveeventitem.cpp index 33f909ecc..26ee346ff 100644 --- a/profile-widget/diveeventitem.cpp +++ b/profile-widget/diveeventitem.cpp @@ -9,6 +9,7 @@ #include "core/gettextfromc.h" #include "core/metrics.h" #include "core/membuffer.h" +#include "core/sample.h" #include "core/subsurface-string.h" #define DEPTH_NOT_FOUND (-2342) diff --git a/qt-models/divecomputerextradatamodel.cpp b/qt-models/divecomputerextradatamodel.cpp index ef16a30f2..d0cf2d98d 100644 --- a/qt-models/divecomputerextradatamodel.cpp +++ b/qt-models/divecomputerextradatamodel.cpp @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 #include "qt-models/divecomputerextradatamodel.h" -#include "core/dive.h" +#include "core/divecomputer.h" +#include "core/extradata.h" #include "core/metrics.h" - ExtraDataModel::ExtraDataModel(QObject *parent) : CleanerTableModel(parent) { //enum Column {KEY, VALUE}; diff --git a/qt-models/diveplannermodel.cpp b/qt-models/diveplannermodel.cpp index 5be22b08d..600816f50 100644 --- a/qt-models/diveplannermodel.cpp +++ b/qt-models/diveplannermodel.cpp @@ -7,6 +7,7 @@ #include "qt-models/models.h" #include "core/device.h" #include "core/qthelper.h" +#include "core/sample.h" #include "core/settings/qPrefDivePlanner.h" #include "core/settings/qPrefUnit.h" #if !defined(SUBSURFACE_TESTING) diff --git a/tests/testparse.cpp b/tests/testparse.cpp index c0ffaeb36..1a655f4e4 100644 --- a/tests/testparse.cpp +++ b/tests/testparse.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include "testparse.h" #include "core/device.h" +#include "core/dive.h" #include "core/divesite.h" #include "core/errorhelper.h" #include "core/trip.h"