2017-04-27 18:24:53 +00:00
|
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2011-09-20 19:40:34 +00:00
|
|
|
|
/* dive.c */
|
|
|
|
|
/* maintains the internal dive list structure */
|
2011-09-03 20:19:26 +00:00
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdio.h>
|
2014-04-17 14:34:21 +00:00
|
|
|
|
#include <stdlib.h>
|
2013-10-07 13:15:52 +00:00
|
|
|
|
#include <limits.h>
|
2013-10-06 15:55:58 +00:00
|
|
|
|
#include "gettext.h"
|
2011-09-03 20:19:26 +00:00
|
|
|
|
#include "dive.h"
|
2014-06-01 19:07:29 +00:00
|
|
|
|
#include "libdivecomputer.h"
|
Make gas use statistics be coherent and more complete
The gas use logic in the dive statistics page is confused.
The SAC case had a special case for "unknown", but only for
the first gas. Other gases had the normal empty case.
Also, the logic was really odd - if you had gases that weren't used (or
pressures not known) intermixed with gases you *did* have pressure for,
the statistics got really confused.
The list of gases showed all gases that we know about during the dive,
but then the gas use and SAC-rate lists wouldn't necessarily match,
because the loops that computed those stopped after the first gas that
didn't have any pressure change.
To make things worse, the first cylinder was special-cased again, so it
all lined up for the single-cylinder case.
This makes all the cylinders act the same way, leaving unknown gas use
(and thus SAC) just empty for that gas.
It also fixes the SAC calculation case where we don't have real samples,
and the profile is a fake profile - possibly with gas changes in between
the fake points. We now make the SAC calculations match what we show -
which is admittedly not at all necessarily what the dive was, but at
least we're consistent.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-07-30 17:08:33 +00:00
|
|
|
|
#include "device.h"
|
2015-06-18 00:58:31 +00:00
|
|
|
|
#include "divelist.h"
|
2015-09-17 14:56:58 +00:00
|
|
|
|
#include "qthelperfromc.h"
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
2014-07-02 18:50:28 +00:00
|
|
|
|
/* one could argue about the best place to have this variable -
|
|
|
|
|
* it's used in the UI, but it seems to make the most sense to have it
|
|
|
|
|
* here */
|
|
|
|
|
struct dive displayed_dive;
|
2015-02-13 20:38:56 +00:00
|
|
|
|
struct dive_site displayed_dive_site;
|
2014-07-02 18:50:28 +00:00
|
|
|
|
|
2013-11-02 01:12:42 +00:00
|
|
|
|
struct tag_entry *g_tag_list = NULL;
|
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
static const char *default_tags[] = {
|
2013-11-19 15:12:34 +00:00
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "boat"), QT_TRANSLATE_NOOP("gettextFromC", "shore"), QT_TRANSLATE_NOOP("gettextFromC", "drift"),
|
2014-02-28 04:09:57 +00:00
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "deep"), QT_TRANSLATE_NOOP("gettextFromC", "cavern"), QT_TRANSLATE_NOOP("gettextFromC", "ice"),
|
2013-11-19 15:12:34 +00:00
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "wreck"), QT_TRANSLATE_NOOP("gettextFromC", "cave"), QT_TRANSLATE_NOOP("gettextFromC", "altitude"),
|
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "pool"), QT_TRANSLATE_NOOP("gettextFromC", "lake"), QT_TRANSLATE_NOOP("gettextFromC", "river"),
|
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "night"), QT_TRANSLATE_NOOP("gettextFromC", "fresh"), QT_TRANSLATE_NOOP("gettextFromC", "student"),
|
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "instructor"), QT_TRANSLATE_NOOP("gettextFromC", "photo"), QT_TRANSLATE_NOOP("gettextFromC", "video"),
|
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "deco")
|
|
|
|
|
};
|
|
|
|
|
|
2014-11-17 14:15:19 +00:00
|
|
|
|
const char *cylinderuse_text[] = {
|
2017-01-23 16:35:27 +00:00
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "OC-gas"), QT_TRANSLATE_NOOP("gettextFromC", "diluent"), QT_TRANSLATE_NOOP("gettextFromC", "oxygen"), QT_TRANSLATE_NOOP("gettextFromC", "not used")
|
2014-11-17 14:15:19 +00:00
|
|
|
|
};
|
2015-01-10 23:01:15 +00:00
|
|
|
|
const char *divemode_text[] = { "OC", "CCR", "PSCR", "Freedive" };
|
2014-11-16 22:11:34 +00:00
|
|
|
|
|
2017-07-26 01:33:10 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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
|
|
|
|
|
* indexes.
|
|
|
|
|
*
|
|
|
|
|
* The "prepare_sample()" function will always copy the sensor indices
|
|
|
|
|
* from the previous sample, so the indexes 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
|
|
|
|
|
* "o2pressure" that is fixed to the Oxygen sensor for a CCR dive.
|
|
|
|
|
*
|
|
|
|
|
* For more complex pressure data, we have to use explicit
|
|
|
|
|
* cylinder indexes for each sample.
|
|
|
|
|
*
|
|
|
|
|
* This function returns a negative number for "no legacy mode",
|
|
|
|
|
* or a non-negative number that indicates the o2 sensor index.
|
|
|
|
|
*/
|
|
|
|
|
int legacy_format_o2pressures(struct dive *dive, struct divecomputer *dc)
|
|
|
|
|
{
|
|
|
|
|
int i, o2sensor;
|
|
|
|
|
|
|
|
|
|
o2sensor = (dc->divemode == CCR) ? get_cylinder_idx_by_use(dive, OXYGEN) : -1;
|
|
|
|
|
for (i = 0; i < dc->samples; i++) {
|
|
|
|
|
struct sample *s = dc->sample + i;
|
|
|
|
|
int seen_pressure = 0, idx;
|
|
|
|
|
|
|
|
|
|
for (idx = 0; idx < MAX_SENSORS; idx++) {
|
|
|
|
|
int sensor = s->sensor[idx];
|
|
|
|
|
pressure_t p = s->pressure[idx];
|
|
|
|
|
|
|
|
|
|
if (!p.mbar)
|
|
|
|
|
continue;
|
|
|
|
|
if (sensor == o2sensor)
|
|
|
|
|
continue;
|
|
|
|
|
if (seen_pressure)
|
|
|
|
|
return -1;
|
|
|
|
|
seen_pressure = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Use legacy mode: if we have no O2 sensor we return a
|
|
|
|
|
* positive sensor index that is guaranmteed to not match
|
|
|
|
|
* any sensor (we encode it as 8 bits).
|
|
|
|
|
*/
|
|
|
|
|
return o2sensor < 0 ? 256 : o2sensor;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-17 18:26:21 +00:00
|
|
|
|
int event_is_gaschange(struct event *ev)
|
|
|
|
|
{
|
|
|
|
|
return ev->type == SAMPLE_EVENT_GASCHANGE ||
|
|
|
|
|
ev->type == SAMPLE_EVENT_GASCHANGE2;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-10 02:18:58 +00:00
|
|
|
|
struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name)
|
2011-09-23 01:02:54 +00:00
|
|
|
|
{
|
2015-01-04 01:21:05 +00:00
|
|
|
|
int gas_index = -1;
|
2011-09-23 01:02:54 +00:00
|
|
|
|
struct event *ev, **p;
|
|
|
|
|
unsigned int size, len = strlen(name);
|
|
|
|
|
|
|
|
|
|
size = sizeof(*ev) + len + 1;
|
|
|
|
|
ev = malloc(size);
|
|
|
|
|
if (!ev)
|
2014-08-17 18:26:21 +00:00
|
|
|
|
return NULL;
|
2011-09-23 01:02:54 +00:00
|
|
|
|
memset(ev, 0, size);
|
|
|
|
|
memcpy(ev->name, name, len);
|
|
|
|
|
ev->time.seconds = time;
|
|
|
|
|
ev->type = type;
|
|
|
|
|
ev->flags = flags;
|
|
|
|
|
ev->value = value;
|
|
|
|
|
|
2014-08-17 18:26:21 +00:00
|
|
|
|
/*
|
|
|
|
|
* Expand the events into a sane format. Currently
|
|
|
|
|
* just gas switches
|
|
|
|
|
*/
|
|
|
|
|
switch (type) {
|
|
|
|
|
case SAMPLE_EVENT_GASCHANGE2:
|
|
|
|
|
/* High 16 bits are He percentage */
|
|
|
|
|
ev->gas.mix.he.permille = (value >> 16) * 10;
|
2015-01-04 01:21:05 +00:00
|
|
|
|
|
|
|
|
|
/* Extension to the GASCHANGE2 format: cylinder index in 'flags' */
|
|
|
|
|
if (flags > 0 && flags <= MAX_CYLINDERS)
|
|
|
|
|
gas_index = flags-1;
|
2014-08-17 18:26:21 +00:00
|
|
|
|
/* Fallthrough */
|
|
|
|
|
case SAMPLE_EVENT_GASCHANGE:
|
|
|
|
|
/* Low 16 bits are O2 percentage */
|
|
|
|
|
ev->gas.mix.o2.permille = (value & 0xffff) * 10;
|
2015-01-04 01:21:05 +00:00
|
|
|
|
ev->gas.index = gas_index;
|
2014-08-17 18:26:21 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
|
p = &dc->events;
|
2013-03-18 01:07:59 +00:00
|
|
|
|
|
|
|
|
|
/* insert in the sorted list of events */
|
2013-04-22 04:00:40 +00:00
|
|
|
|
while (*p && (*p)->time.seconds <= time)
|
2011-09-23 01:02:54 +00:00
|
|
|
|
p = &(*p)->next;
|
2013-03-18 01:07:59 +00:00
|
|
|
|
ev->next = *p;
|
2011-09-23 01:02:54 +00:00
|
|
|
|
*p = ev;
|
2011-10-25 07:29:19 +00:00
|
|
|
|
remember_event(name);
|
2014-08-17 18:26:21 +00:00
|
|
|
|
return ev;
|
2011-09-23 01:02:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-12 19:21:05 +00:00
|
|
|
|
static int same_event(struct event *a, struct event *b)
|
|
|
|
|
{
|
|
|
|
|
if (a->time.seconds != b->time.seconds)
|
|
|
|
|
return 0;
|
|
|
|
|
if (a->type != b->type)
|
|
|
|
|
return 0;
|
|
|
|
|
if (a->flags != b->flags)
|
|
|
|
|
return 0;
|
|
|
|
|
if (a->value != b->value)
|
|
|
|
|
return 0;
|
|
|
|
|
return !strcmp(a->name, b->name);
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-11 11:25:52 +00:00
|
|
|
|
void remove_event(struct event *event)
|
2014-05-21 15:06:02 +00:00
|
|
|
|
{
|
|
|
|
|
struct event **ep = ¤t_dc->events;
|
2014-07-12 19:21:05 +00:00
|
|
|
|
while (ep && !same_event(*ep, event))
|
2014-05-21 15:06:02 +00:00
|
|
|
|
ep = &(*ep)->next;
|
|
|
|
|
if (ep) {
|
2014-07-29 15:50:06 +00:00
|
|
|
|
/* we can't link directly with event->next
|
|
|
|
|
* because 'event' can be a copy from another
|
|
|
|
|
* dive (for instance the displayed_dive
|
|
|
|
|
* that we use on the interface to show things). */
|
|
|
|
|
struct event *temp = (*ep)->next;
|
|
|
|
|
free(*ep);
|
|
|
|
|
*ep = temp;
|
2014-05-21 15:06:02 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-12 19:48:27 +00:00
|
|
|
|
/* 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
|
|
|
|
|
* WARNING, WARNING... part of this divecomputer on this dive! */
|
2014-10-11 11:25:52 +00:00
|
|
|
|
void update_event_name(struct dive *d, struct event *event, char *name)
|
2014-07-12 19:48:27 +00:00
|
|
|
|
{
|
|
|
|
|
if (!d || !event)
|
|
|
|
|
return;
|
|
|
|
|
struct divecomputer *dc = get_dive_dc(d, dc_number);
|
|
|
|
|
if (!dc)
|
|
|
|
|
return;
|
|
|
|
|
struct event **removep = &dc->events;
|
|
|
|
|
struct event *remove;
|
|
|
|
|
while ((*removep)->next && !same_event(*removep, event))
|
|
|
|
|
removep = &(*removep)->next;
|
|
|
|
|
if (!same_event(*removep, event))
|
|
|
|
|
return;
|
|
|
|
|
remove = *removep;
|
|
|
|
|
*removep = (*removep)->next;
|
|
|
|
|
add_event(dc, event->time.seconds, event->type, event->flags, event->value, name);
|
|
|
|
|
free(remove);
|
2016-04-03 22:31:59 +00:00
|
|
|
|
invalidate_dive_cache(d);
|
2014-07-12 19:48:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-11-07 06:02:22 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-01 19:07:29 +00:00
|
|
|
|
/* this returns a pointer to static variable - so use it right away after calling */
|
2016-04-02 20:06:54 +00:00
|
|
|
|
struct gasmix *get_gasmix_from_event(struct dive *dive, struct event *ev)
|
2014-06-01 19:07:29 +00:00
|
|
|
|
{
|
2014-08-17 18:26:21 +00:00
|
|
|
|
static struct gasmix dummy;
|
Start using the actual cylinder data for gas switch events
Now that gas switch events always have indices into the cylinder table,
start using that to look up the gas mix from the cylinders rather than
from the gas switch event itself. In other words, the cylinder index is
now the primary data for gas switch events.
This means that now as you change the cylinder information, the gas
switch events will automatically update to reflect those changes.
Note that on loading data from the outside (either from a xml file, from
a git/cloud account, or from a dive computer), we may or may not
initially have an index for the gas change event. The external data may
be from an older version of subsurface, or it may be from a
libdivecomputer download that just doesn't give index data at all.
In that case, we will do:
- if there is no index, but there is explicit gas mix information, we
will look up the index based on that gas mix, picking the cylinder
that has the closest mix.
- if there isn't even explicit gas mix data, so we only have the event
value from libdivecomputer, we will turn that value into a gasmix,
and use that to look up the cylinder index as above.
- if no valid cylinder information is available at all, gas switch
events will just be dropped.
When saving the data, we now always save the cylinder index, and the gas
mix associated with that cylinder (that gas mix will be ignored on load,
since the index is the primary, but it makes the event much easier to
read).
It is worth noting we do not modify the libdivecomputer value, even if
the gasmix has changed, so that remains as a record of the original
download.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-02 21:07:06 +00:00
|
|
|
|
if (ev && event_is_gaschange(ev)) {
|
|
|
|
|
int index = ev->gas.index;
|
|
|
|
|
if (index >= 0 && index <= MAX_CYLINDERS)
|
|
|
|
|
return &dive->cylinder[index].gasmix;
|
2014-08-17 18:26:21 +00:00
|
|
|
|
return &ev->gas.mix;
|
Start using the actual cylinder data for gas switch events
Now that gas switch events always have indices into the cylinder table,
start using that to look up the gas mix from the cylinders rather than
from the gas switch event itself. In other words, the cylinder index is
now the primary data for gas switch events.
This means that now as you change the cylinder information, the gas
switch events will automatically update to reflect those changes.
Note that on loading data from the outside (either from a xml file, from
a git/cloud account, or from a dive computer), we may or may not
initially have an index for the gas change event. The external data may
be from an older version of subsurface, or it may be from a
libdivecomputer download that just doesn't give index data at all.
In that case, we will do:
- if there is no index, but there is explicit gas mix information, we
will look up the index based on that gas mix, picking the cylinder
that has the closest mix.
- if there isn't even explicit gas mix data, so we only have the event
value from libdivecomputer, we will turn that value into a gasmix,
and use that to look up the cylinder index as above.
- if no valid cylinder information is available at all, gas switch
events will just be dropped.
When saving the data, we now always save the cylinder index, and the gas
mix associated with that cylinder (that gas mix will be ignored on load,
since the index is the primary, but it makes the event much easier to
read).
It is worth noting we do not modify the libdivecomputer value, even if
the gasmix has changed, so that remains as a record of the original
download.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-02 21:07:06 +00:00
|
|
|
|
}
|
2014-08-17 18:26:21 +00:00
|
|
|
|
|
|
|
|
|
return &dummy;
|
2014-06-01 19:07:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-05-29 20:13:11 +00:00
|
|
|
|
int get_pressure_units(int mb, const char **units)
|
2011-11-02 03:13:14 +00:00
|
|
|
|
{
|
|
|
|
|
int pressure;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
const char *unit;
|
2013-01-11 01:26:10 +00:00
|
|
|
|
struct units *units_p = get_units();
|
2011-11-02 03:13:14 +00:00
|
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
|
switch (units_p->pressure) {
|
2011-11-02 03:13:14 +00:00
|
|
|
|
case PASCAL:
|
|
|
|
|
pressure = mb * 100;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
unit = translate("gettextFromC", "pascal");
|
2011-11-02 03:13:14 +00:00
|
|
|
|
break;
|
|
|
|
|
case BAR:
|
2013-12-20 17:37:56 +00:00
|
|
|
|
default:
|
2011-11-02 03:13:14 +00:00
|
|
|
|
pressure = (mb + 500) / 1000;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
unit = translate("gettextFromC", "bar");
|
2011-11-02 03:13:14 +00:00
|
|
|
|
break;
|
|
|
|
|
case PSI:
|
|
|
|
|
pressure = mbar_to_PSI(mb);
|
2014-02-28 04:09:57 +00:00
|
|
|
|
unit = translate("gettextFromC", "psi");
|
2011-11-02 03:13:14 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (units)
|
|
|
|
|
*units = unit;
|
|
|
|
|
return pressure;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-01 18:39:52 +00:00
|
|
|
|
double get_temp_units(unsigned int mk, const char **units)
|
|
|
|
|
{
|
|
|
|
|
double deg;
|
|
|
|
|
const char *unit;
|
2013-01-11 01:26:10 +00:00
|
|
|
|
struct units *units_p = get_units();
|
2011-11-01 18:39:52 +00:00
|
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
|
if (units_p->temperature == FAHRENHEIT) {
|
2011-11-01 18:39:52 +00:00
|
|
|
|
deg = mkelvin_to_F(mk);
|
|
|
|
|
unit = UTF8_DEGREE "F";
|
|
|
|
|
} else {
|
|
|
|
|
deg = mkelvin_to_C(mk);
|
|
|
|
|
unit = UTF8_DEGREE "C";
|
|
|
|
|
}
|
|
|
|
|
if (units)
|
|
|
|
|
*units = unit;
|
|
|
|
|
return deg;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-02 02:56:14 +00:00
|
|
|
|
double get_volume_units(unsigned int ml, int *frac, const char **units)
|
|
|
|
|
{
|
|
|
|
|
int decimals;
|
|
|
|
|
double vol;
|
|
|
|
|
const char *unit;
|
2013-01-11 01:26:10 +00:00
|
|
|
|
struct units *units_p = get_units();
|
2011-11-02 02:56:14 +00:00
|
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
|
switch (units_p->volume) {
|
2011-11-02 02:56:14 +00:00
|
|
|
|
case LITER:
|
2013-12-20 17:37:56 +00:00
|
|
|
|
default:
|
2011-11-02 02:56:14 +00:00
|
|
|
|
vol = ml / 1000.0;
|
2014-06-06 19:13:54 +00:00
|
|
|
|
unit = translate("gettextFromC", "ℓ");
|
2011-11-02 02:56:14 +00:00
|
|
|
|
decimals = 1;
|
|
|
|
|
break;
|
|
|
|
|
case CUFT:
|
|
|
|
|
vol = ml_to_cuft(ml);
|
2014-02-28 04:09:57 +00:00
|
|
|
|
unit = translate("gettextFromC", "cuft");
|
2011-11-02 02:56:14 +00:00
|
|
|
|
decimals = 2;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (frac)
|
|
|
|
|
*frac = decimals;
|
|
|
|
|
if (units)
|
|
|
|
|
*units = unit;
|
|
|
|
|
return vol;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-19 16:13:55 +00:00
|
|
|
|
int units_to_sac(double volume)
|
2014-08-06 08:16:12 +00:00
|
|
|
|
{
|
2014-10-11 11:25:52 +00:00
|
|
|
|
if (get_units()->volume == CUFT)
|
2017-03-08 06:41:41 +00:00
|
|
|
|
return lrint(cuft_to_l(volume) * 1000.0);
|
2014-08-06 08:16:12 +00:00
|
|
|
|
else
|
2017-03-08 06:41:41 +00:00
|
|
|
|
return lrint(volume * 1000);
|
2014-08-06 08:16:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-10 06:22:31 +00:00
|
|
|
|
depth_t units_to_depth(double depth)
|
2013-10-11 23:39:40 +00:00
|
|
|
|
{
|
2017-03-10 06:22:31 +00:00
|
|
|
|
depth_t internaldepth;
|
|
|
|
|
if (get_units()->length == METERS) {
|
2017-03-11 16:41:41 +00:00
|
|
|
|
internaldepth.mm = lrint(depth * 1000);
|
2017-03-10 06:22:31 +00:00
|
|
|
|
} else {
|
|
|
|
|
internaldepth.mm = feet_to_mm(depth);
|
|
|
|
|
}
|
|
|
|
|
return internaldepth;
|
2013-10-11 23:39:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-02-28 23:38:42 +00:00
|
|
|
|
double get_depth_units(int mm, int *frac, const char **units)
|
2011-09-21 19:12:54 +00:00
|
|
|
|
{
|
|
|
|
|
int decimals;
|
|
|
|
|
double d;
|
|
|
|
|
const char *unit;
|
2013-01-11 01:26:10 +00:00
|
|
|
|
struct units *units_p = get_units();
|
2011-09-21 19:12:54 +00:00
|
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
|
switch (units_p->length) {
|
2011-09-21 19:12:54 +00:00
|
|
|
|
case METERS:
|
2013-12-20 17:37:56 +00:00
|
|
|
|
default:
|
2011-09-21 19:12:54 +00:00
|
|
|
|
d = mm / 1000.0;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
unit = translate("gettextFromC", "m");
|
2011-09-21 19:12:54 +00:00
|
|
|
|
decimals = d < 20;
|
|
|
|
|
break;
|
|
|
|
|
case FEET:
|
|
|
|
|
d = mm_to_feet(mm);
|
2014-02-28 04:09:57 +00:00
|
|
|
|
unit = translate("gettextFromC", "ft");
|
2011-09-21 19:12:54 +00:00
|
|
|
|
decimals = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (frac)
|
|
|
|
|
*frac = decimals;
|
|
|
|
|
if (units)
|
|
|
|
|
*units = unit;
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-04 05:57:48 +00:00
|
|
|
|
double get_vertical_speed_units(unsigned int mms, int *frac, const char **units)
|
|
|
|
|
{
|
|
|
|
|
double d;
|
|
|
|
|
const char *unit;
|
|
|
|
|
const struct units *units_p = get_units();
|
|
|
|
|
const double time_factor = units_p->vertical_speed_time == MINUTES ? 60.0 : 1.0;
|
|
|
|
|
|
|
|
|
|
switch (units_p->length) {
|
|
|
|
|
case METERS:
|
2013-12-20 17:37:56 +00:00
|
|
|
|
default:
|
2013-10-04 05:57:48 +00:00
|
|
|
|
d = mms / 1000.0 * time_factor;
|
2014-07-16 16:35:48 +00:00
|
|
|
|
if (units_p->vertical_speed_time == MINUTES)
|
|
|
|
|
unit = translate("gettextFromC", "m/min");
|
|
|
|
|
else
|
|
|
|
|
unit = translate("gettextFromC", "m/s");
|
2013-10-04 05:57:48 +00:00
|
|
|
|
break;
|
|
|
|
|
case FEET:
|
|
|
|
|
d = mm_to_feet(mms) * time_factor;
|
2014-07-16 16:35:48 +00:00
|
|
|
|
if (units_p->vertical_speed_time == MINUTES)
|
|
|
|
|
unit = translate("gettextFromC", "ft/min");
|
|
|
|
|
else
|
|
|
|
|
unit = translate("gettextFromC", "ft/s");
|
2013-10-04 05:57:48 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (frac)
|
|
|
|
|
*frac = d < 10;
|
|
|
|
|
if (units)
|
|
|
|
|
*units = unit;
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-07 18:24:40 +00:00
|
|
|
|
double get_weight_units(unsigned int grams, int *frac, const char **units)
|
|
|
|
|
{
|
|
|
|
|
int decimals;
|
|
|
|
|
double value;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
const char *unit;
|
2013-01-11 01:26:10 +00:00
|
|
|
|
struct units *units_p = get_units();
|
2012-08-07 18:24:40 +00:00
|
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
|
if (units_p->weight == LBS) {
|
2012-08-07 18:24:40 +00:00
|
|
|
|
value = grams_to_lbs(grams);
|
2014-02-28 04:09:57 +00:00
|
|
|
|
unit = translate("gettextFromC", "lbs");
|
2012-08-07 18:24:40 +00:00
|
|
|
|
decimals = 0;
|
|
|
|
|
} else {
|
|
|
|
|
value = grams / 1000.0;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
unit = translate("gettextFromC", "kg");
|
2012-08-07 18:24:40 +00:00
|
|
|
|
decimals = 1;
|
|
|
|
|
}
|
|
|
|
|
if (frac)
|
|
|
|
|
*frac = decimals;
|
|
|
|
|
if (units)
|
|
|
|
|
*units = unit;
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-24 20:58:39 +00:00
|
|
|
|
bool has_hr_data(struct divecomputer *dc)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
struct sample *sample;
|
|
|
|
|
|
|
|
|
|
if (!dc)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
sample = dc->sample;
|
|
|
|
|
for (i = 0; i < dc->samples; i++)
|
|
|
|
|
if (sample[i].heartbeat)
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-12 19:56:34 +00:00
|
|
|
|
struct dive *alloc_dive(void)
|
|
|
|
|
{
|
|
|
|
|
struct dive *dive;
|
|
|
|
|
|
2012-11-24 02:05:38 +00:00
|
|
|
|
dive = malloc(sizeof(*dive));
|
2011-09-12 19:56:34 +00:00
|
|
|
|
if (!dive)
|
|
|
|
|
exit(1);
|
2012-11-24 02:05:38 +00:00
|
|
|
|
memset(dive, 0, sizeof(*dive));
|
2014-05-18 21:53:28 +00:00
|
|
|
|
dive->id = dive_getUniqID(dive);
|
2011-09-12 19:56:34 +00:00
|
|
|
|
return dive;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-02 22:29:02 +00:00
|
|
|
|
static void free_dc(struct divecomputer *dc);
|
Make 'clear_dive()' free the primary dive computer data properly
Our primary dive computer really is special, not just because it's the
first one: it's directly embedded in the "struct dive", and so if you
just walk the divecomputer list, you'll miss it, because it's not _on_
the list, it is the very head _of_ the list.
We had that bug in copy_dive(), and it turns out we have it in
clear_dive() too: clear_dive() would free all the dive computers on the
list, but not the actual primary one.
This is a minor memory leak, no more, so it's not exactly critial, but
let's just do it right.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-11 21:25:03 +00:00
|
|
|
|
static void free_dc_contents(struct divecomputer *dc);
|
2014-07-02 22:29:02 +00:00
|
|
|
|
static void free_pic(struct picture *picture);
|
|
|
|
|
|
|
|
|
|
/* this is very different from the copy_divecomputer later in this file;
|
|
|
|
|
* this function actually makes full copies of the content */
|
|
|
|
|
static void copy_dc(struct divecomputer *sdc, struct divecomputer *ddc)
|
|
|
|
|
{
|
|
|
|
|
*ddc = *sdc;
|
2014-07-03 04:05:22 +00:00
|
|
|
|
ddc->model = copy_string(sdc->model);
|
2014-07-02 22:29:02 +00:00
|
|
|
|
copy_samples(sdc, ddc);
|
|
|
|
|
copy_events(sdc, ddc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* copy an element in a list of pictures */
|
|
|
|
|
static void copy_pl(struct picture *sp, struct picture *dp)
|
|
|
|
|
{
|
|
|
|
|
*dp = *sp;
|
2014-07-03 04:05:22 +00:00
|
|
|
|
dp->filename = copy_string(sp->filename);
|
2015-02-26 13:39:42 +00:00
|
|
|
|
dp->hash = copy_string(sp->hash);
|
2014-07-02 22:29:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* copy an element in a list of tags */
|
|
|
|
|
static void copy_tl(struct tag_entry *st, struct tag_entry *dt)
|
|
|
|
|
{
|
|
|
|
|
dt->tag = malloc(sizeof(struct divetag));
|
2014-07-03 04:05:22 +00:00
|
|
|
|
dt->tag->name = copy_string(st->tag->name);
|
|
|
|
|
dt->tag->source = copy_string(st->tag->source);
|
2014-07-02 22:29:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Clear everything but the first element;
|
|
|
|
|
* this works for taglist, picturelist, even dive computers */
|
2014-10-11 11:25:52 +00:00
|
|
|
|
#define STRUCTURED_LIST_FREE(_type, _start, _free) \
|
|
|
|
|
{ \
|
|
|
|
|
_type *_ptr = _start; \
|
|
|
|
|
while (_ptr) { \
|
|
|
|
|
_type *_next = _ptr->next; \
|
|
|
|
|
_free(_ptr); \
|
|
|
|
|
_ptr = _next; \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define STRUCTURED_LIST_COPY(_type, _first, _dest, _cpy) \
|
|
|
|
|
{ \
|
|
|
|
|
_type *_sptr = _first; \
|
|
|
|
|
_type **_dptr = &_dest; \
|
|
|
|
|
while (_sptr) { \
|
|
|
|
|
*_dptr = malloc(sizeof(_type)); \
|
|
|
|
|
_cpy(_sptr, *_dptr); \
|
|
|
|
|
_sptr = _sptr->next; \
|
|
|
|
|
_dptr = &(*_dptr)->next; \
|
|
|
|
|
} \
|
|
|
|
|
*_dptr = 0; \
|
2014-07-02 22:29:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* copy_dive makes duplicates of many components of a dive;
|
|
|
|
|
* in order not to leak memory, we need to free those .
|
|
|
|
|
* copy_dive doesn't play with the divetrip and forward/backward pointers
|
|
|
|
|
* so we can ignore those */
|
|
|
|
|
void clear_dive(struct dive *d)
|
|
|
|
|
{
|
|
|
|
|
if (!d)
|
|
|
|
|
return;
|
|
|
|
|
/* free the strings */
|
|
|
|
|
free(d->buddy);
|
|
|
|
|
free(d->divemaster);
|
|
|
|
|
free(d->notes);
|
|
|
|
|
free(d->suit);
|
|
|
|
|
/* free tags, additional dive computers, and pictures */
|
|
|
|
|
taglist_free(d->tag_list);
|
Make 'clear_dive()' free the primary dive computer data properly
Our primary dive computer really is special, not just because it's the
first one: it's directly embedded in the "struct dive", and so if you
just walk the divecomputer list, you'll miss it, because it's not _on_
the list, it is the very head _of_ the list.
We had that bug in copy_dive(), and it turns out we have it in
clear_dive() too: clear_dive() would free all the dive computers on the
list, but not the actual primary one.
This is a minor memory leak, no more, so it's not exactly critial, but
let's just do it right.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-11 21:25:03 +00:00
|
|
|
|
free_dc_contents(&d->dc);
|
2014-07-02 22:29:02 +00:00
|
|
|
|
STRUCTURED_LIST_FREE(struct divecomputer, d->dc.next, free_dc);
|
|
|
|
|
STRUCTURED_LIST_FREE(struct picture, d->picture_list, free_pic);
|
2014-11-14 21:33:12 +00:00
|
|
|
|
for (int i = 0; i < MAX_CYLINDERS; i++)
|
|
|
|
|
free((void *)d->cylinder[i].type.description);
|
|
|
|
|
for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++)
|
|
|
|
|
free((void *)d->weightsystem[i].description);
|
2014-07-02 22:29:02 +00:00
|
|
|
|
memset(d, 0, sizeof(struct dive));
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-03 20:34:27 +00:00
|
|
|
|
/* make a true copy that is independent of the source dive;
|
|
|
|
|
* all data structures are duplicated, so the copy can be modified without
|
|
|
|
|
* any impact on the source */
|
2014-07-02 22:29:02 +00:00
|
|
|
|
void copy_dive(struct dive *s, struct dive *d)
|
|
|
|
|
{
|
|
|
|
|
clear_dive(d);
|
|
|
|
|
/* simply copy things over, but then make actual copies of the
|
|
|
|
|
* relevant components that are referenced through pointers,
|
|
|
|
|
* so all the strings and the structured lists */
|
|
|
|
|
*d = *s;
|
2016-04-03 22:31:59 +00:00
|
|
|
|
invalidate_dive_cache(d);
|
2014-07-03 04:05:22 +00:00
|
|
|
|
d->buddy = copy_string(s->buddy);
|
|
|
|
|
d->divemaster = copy_string(s->divemaster);
|
|
|
|
|
d->notes = copy_string(s->notes);
|
|
|
|
|
d->suit = copy_string(s->suit);
|
2014-11-14 21:33:12 +00:00
|
|
|
|
for (int i = 0; i < MAX_CYLINDERS; i++)
|
|
|
|
|
d->cylinder[i].type.description = copy_string(s->cylinder[i].type.description);
|
|
|
|
|
for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++)
|
|
|
|
|
d->weightsystem[i].description = copy_string(s->weightsystem[i].description);
|
2014-07-02 22:29:02 +00:00
|
|
|
|
STRUCTURED_LIST_COPY(struct picture, s->picture_list, d->picture_list, copy_pl);
|
|
|
|
|
STRUCTURED_LIST_COPY(struct tag_entry, s->tag_list, d->tag_list, copy_tl);
|
2016-04-03 22:31:59 +00:00
|
|
|
|
|
|
|
|
|
// Copy the first dc explicitly, then the list of subsequent dc's
|
|
|
|
|
copy_dc(&s->dc, &d->dc);
|
2014-07-06 18:02:28 +00:00
|
|
|
|
STRUCTURED_LIST_COPY(struct divecomputer, s->dc.next, d->dc.next, copy_dc);
|
2014-07-02 22:29:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-03 20:34:27 +00:00
|
|
|
|
/* make a clone of the source dive and clean out the source dive;
|
|
|
|
|
* this is specifically so we can create a dive in the displayed_dive and then
|
|
|
|
|
* add it to the divelist.
|
|
|
|
|
* Note the difference to copy_dive() / clean_dive() */
|
|
|
|
|
struct dive *clone_dive(struct dive *s)
|
|
|
|
|
{
|
|
|
|
|
struct dive *dive = alloc_dive();
|
2014-10-11 11:25:52 +00:00
|
|
|
|
*dive = *s; // so all the pointers in dive point to the things s pointed to
|
2014-07-03 20:34:27 +00:00
|
|
|
|
memset(s, 0, sizeof(struct dive)); // and now the pointers in s are gone
|
|
|
|
|
return dive;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-16 13:55:31 +00:00
|
|
|
|
#define CONDITIONAL_COPY_STRING(_component) \
|
|
|
|
|
if (what._component) \
|
|
|
|
|
d->_component = copy_string(s->_component)
|
|
|
|
|
|
|
|
|
|
// copy elements, depending on bits in what that are set
|
2014-08-17 00:33:09 +00:00
|
|
|
|
void selective_copy_dive(struct dive *s, struct dive *d, struct dive_components what, bool clear)
|
2014-08-16 13:55:31 +00:00
|
|
|
|
{
|
2014-08-17 00:33:09 +00:00
|
|
|
|
if (clear)
|
|
|
|
|
clear_dive(d);
|
2014-08-16 13:55:31 +00:00
|
|
|
|
CONDITIONAL_COPY_STRING(notes);
|
|
|
|
|
CONDITIONAL_COPY_STRING(divemaster);
|
|
|
|
|
CONDITIONAL_COPY_STRING(buddy);
|
|
|
|
|
CONDITIONAL_COPY_STRING(suit);
|
|
|
|
|
if (what.rating)
|
|
|
|
|
d->rating = s->rating;
|
|
|
|
|
if (what.visibility)
|
|
|
|
|
d->visibility = s->visibility;
|
2015-02-13 02:52:54 +00:00
|
|
|
|
if (what.divesite)
|
|
|
|
|
d->dive_site_uuid = s->dive_site_uuid;
|
2014-08-16 13:55:31 +00:00
|
|
|
|
if (what.tags)
|
|
|
|
|
STRUCTURED_LIST_COPY(struct tag_entry, s->tag_list, d->tag_list, copy_tl);
|
|
|
|
|
if (what.cylinders)
|
|
|
|
|
copy_cylinders(s, d, false);
|
|
|
|
|
if (what.weights)
|
2014-11-17 11:30:49 +00:00
|
|
|
|
for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) {
|
|
|
|
|
free((void *)d->weightsystem[i].description);
|
2014-08-16 13:55:31 +00:00
|
|
|
|
d->weightsystem[i] = s->weightsystem[i];
|
2014-11-17 11:30:49 +00:00
|
|
|
|
d->weightsystem[i].description = copy_string(s->weightsystem[i].description);
|
|
|
|
|
}
|
2014-08-16 13:55:31 +00:00
|
|
|
|
}
|
|
|
|
|
#undef CONDITIONAL_COPY_STRING
|
|
|
|
|
|
2015-12-04 05:42:23 +00:00
|
|
|
|
struct event *clone_event(const struct event *src_ev)
|
|
|
|
|
{
|
|
|
|
|
struct event *ev;
|
|
|
|
|
if (!src_ev)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
size_t size = sizeof(*src_ev) + strlen(src_ev->name) + 1;
|
|
|
|
|
ev = (struct event*) malloc(size);
|
|
|
|
|
if (!ev)
|
|
|
|
|
exit(1);
|
|
|
|
|
memcpy(ev, src_ev, size);
|
|
|
|
|
ev->next = NULL;
|
|
|
|
|
|
|
|
|
|
return ev;
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-12 18:49:18 +00:00
|
|
|
|
/* copies all events in this dive computer */
|
2014-07-02 22:29:02 +00:00
|
|
|
|
void copy_events(struct divecomputer *s, struct divecomputer *d)
|
2013-11-08 09:15:04 +00:00
|
|
|
|
{
|
2014-10-12 18:49:18 +00:00
|
|
|
|
struct event *ev, **pev;
|
2013-11-08 09:15:04 +00:00
|
|
|
|
if (!s || !d)
|
|
|
|
|
return;
|
2014-07-02 22:29:02 +00:00
|
|
|
|
ev = s->events;
|
2014-10-12 18:49:18 +00:00
|
|
|
|
pev = &d->events;
|
2013-11-08 09:15:04 +00:00
|
|
|
|
while (ev != NULL) {
|
2015-12-04 05:42:23 +00:00
|
|
|
|
struct event *new_ev = clone_event(ev);
|
2014-10-12 18:49:18 +00:00
|
|
|
|
*pev = new_ev;
|
|
|
|
|
pev = &new_ev->next;
|
2013-11-08 09:15:04 +00:00
|
|
|
|
ev = ev->next;
|
|
|
|
|
}
|
2014-10-12 18:49:18 +00:00
|
|
|
|
*pev = NULL;
|
2013-11-08 09:15:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
Initial implementation of git save format
This saves the dive data into a git object repository instead of a
single XML file.
We create a git object tree with each dive as a separate file,
hierarchically by trip and date.
NOTE 1: This largely duplicates the XML saving code, because trying to
share it seemed just too painful: the logic is very similar, but the
details of the actual strings end up differing sufficiently that there
are tons of trivial differences.
The git save format is line-based with minimal quoting, while XML quotes
everything with either "<..\>" or using single quotes around attributes.
NOTE 2: You currently need a dummy "file" to save to, which points to
the real save location: the git repository and branch to be used. We
should make this a config thing, but for testing, do something like
this:
echo git /home/torvalds/scuba:linus > git-test
to create that git information file, and when you use "Save To" and
specify "git-test" as the file to save to, subsurface will use the new
git save logic to save to the branch "linus" in the repository found at
"/home/torvalds/scuba".
NOTE 3: The git save format uses just the git object directory, it does
*not* check out the result in any git working tree or index. So after
you do a save, you can do
git log -p linus
to see what actually happened in that branch, but it will not affect any
actual checked-out state in the repository.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-06 21:28:39 +00:00
|
|
|
|
int nr_cylinders(struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
int nr;
|
|
|
|
|
|
|
|
|
|
for (nr = MAX_CYLINDERS; nr; --nr) {
|
|
|
|
|
cylinder_t *cylinder = dive->cylinder + nr - 1;
|
|
|
|
|
if (!cylinder_nodata(cylinder))
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return nr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int nr_weightsystems(struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
int nr;
|
|
|
|
|
|
|
|
|
|
for (nr = MAX_WEIGHTSYSTEMS; nr; --nr) {
|
|
|
|
|
weightsystem_t *ws = dive->weightsystem + nr - 1;
|
|
|
|
|
if (!weightsystem_none(ws))
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return nr;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-16 13:55:31 +00:00
|
|
|
|
/* copy the equipment data part of the cylinders */
|
2014-06-04 06:09:12 +00:00
|
|
|
|
void copy_cylinders(struct dive *s, struct dive *d, bool used_only)
|
2013-11-07 08:25:42 +00:00
|
|
|
|
{
|
2014-12-30 23:00:24 +00:00
|
|
|
|
int i,j;
|
2013-11-07 08:25:42 +00:00
|
|
|
|
if (!s || !d)
|
|
|
|
|
return;
|
2014-08-16 13:55:31 +00:00
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
2014-11-17 11:30:49 +00:00
|
|
|
|
free((void *)d->cylinder[i].type.description);
|
2014-08-16 13:55:31 +00:00
|
|
|
|
memset(&d->cylinder[i], 0, sizeof(cylinder_t));
|
2014-12-30 23:00:24 +00:00
|
|
|
|
}
|
|
|
|
|
for (i = j = 0; i < MAX_CYLINDERS; i++) {
|
2017-10-05 18:04:07 +00:00
|
|
|
|
if (!used_only || is_cylinder_used(s, i) || s->cylinder[i].cylinder_use == NOT_USED) {
|
2014-12-30 23:00:24 +00:00
|
|
|
|
d->cylinder[j].type = s->cylinder[i].type;
|
|
|
|
|
d->cylinder[j].type.description = copy_string(s->cylinder[i].type.description);
|
|
|
|
|
d->cylinder[j].gasmix = s->cylinder[i].gasmix;
|
|
|
|
|
d->cylinder[j].depth = s->cylinder[i].depth;
|
|
|
|
|
d->cylinder[j].cylinder_use = s->cylinder[i].cylinder_use;
|
|
|
|
|
d->cylinder[j].manually_added = true;
|
|
|
|
|
j++;
|
2014-08-16 13:55:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2013-11-07 08:25:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-11-17 13:51:19 +00:00
|
|
|
|
int cylinderuse_from_text(const char *text)
|
|
|
|
|
{
|
|
|
|
|
for (enum cylinderuse i = 0; i < NUM_GAS_USE; i++) {
|
2014-11-17 14:15:19 +00:00
|
|
|
|
if (same_string(text, cylinderuse_text[i]) || same_string(text, translate("gettextFromC", cylinderuse_text[i])))
|
2014-11-17 13:51:19 +00:00
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-02 22:29:02 +00:00
|
|
|
|
void copy_samples(struct divecomputer *s, struct divecomputer *d)
|
2013-09-19 04:33:39 +00:00
|
|
|
|
{
|
|
|
|
|
/* 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;
|
2014-07-02 22:29:02 +00:00
|
|
|
|
int nr = s->samples;
|
|
|
|
|
d->samples = nr;
|
2014-12-12 07:59:11 +00:00
|
|
|
|
d->alloc_samples = nr;
|
2014-12-12 07:59:12 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
2014-07-02 22:29:02 +00:00
|
|
|
|
d->sample = malloc(nr * sizeof(struct sample));
|
|
|
|
|
if (d->sample)
|
|
|
|
|
memcpy(d->sample, s->sample, nr * sizeof(struct sample));
|
2013-09-19 04:33:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
|
struct sample *prepare_sample(struct divecomputer *dc)
|
2011-09-12 19:56:34 +00:00
|
|
|
|
{
|
2012-11-24 02:51:27 +00:00
|
|
|
|
if (dc) {
|
|
|
|
|
int nr = dc->samples;
|
|
|
|
|
int alloc_samples = dc->alloc_samples;
|
2011-09-12 19:56:34 +00:00
|
|
|
|
struct sample *sample;
|
|
|
|
|
if (nr >= alloc_samples) {
|
2012-11-24 02:05:38 +00:00
|
|
|
|
struct sample *newsamples;
|
2011-09-12 19:56:34 +00:00
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
alloc_samples = (alloc_samples * 3) / 2 + 10;
|
2012-11-24 02:51:27 +00:00
|
|
|
|
newsamples = realloc(dc->sample, alloc_samples * sizeof(struct sample));
|
2012-11-24 02:05:38 +00:00
|
|
|
|
if (!newsamples)
|
2011-09-12 19:56:34 +00:00
|
|
|
|
return NULL;
|
2012-11-24 02:51:27 +00:00
|
|
|
|
dc->alloc_samples = alloc_samples;
|
|
|
|
|
dc->sample = newsamples;
|
2011-09-12 19:56:34 +00:00
|
|
|
|
}
|
2012-11-24 02:51:27 +00:00
|
|
|
|
sample = dc->sample + nr;
|
2011-09-12 19:56:34 +00:00
|
|
|
|
memset(sample, 0, sizeof(*sample));
|
Try to sanely download multiple concurrent cylinder pressures
This tries to sanely handle the case of a dive computer reporting
multiple cylinder pressures concurrently.
NOTE! There are various "interesting" situations that this whole issue
brings up:
- some dive computers may report more cylinder pressures than we have
slots for.
Currently we will drop such pressures on the floor if they come for
the same sample, but if they end up being spread across multiple
samples we will end up re-using the slots with different sensor
indexes.
That kind of slot re-use may or may not end up confusing other
subsurface logic - for example, make things believe there was a
cylidner change event.
- some dive computers might send only one sample at a time, but switch
*which* sample they send on a gas switch event. If they also report
the correct sensor number, we'll now start reporting that pressure in
the second slot.
This should all be fine, and is the RightThing(tm) to do, but is
different from what we used to do when we only ever used a single
slot.
- When people actually use multiple sensors, our old save format will
start to need fixing. Right now our save format comes from the CCR
model where the second sensor was always the Oxygen sensor.
We save that pressure fine (except we save it as "o2pressure" - just
an odd historical naming artifact), but we do *not* save the actual
sensor index, because in our traditional format that was always
implicit in the data ("it's the oxygen cylinder").
so while this code hopefully makes our libdivecomputer download do the
right thing, there *will* be further fallout from having multiple
cylinder pressure sensors. We're not done yet.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-24 18:55:47 +00:00
|
|
|
|
|
|
|
|
|
// Copy the sensor numbers - but not the pressure values
|
|
|
|
|
// from the previous sample if any.
|
|
|
|
|
if (nr) {
|
|
|
|
|
sample->sensor[0] = sample[-1].sensor[0];
|
|
|
|
|
sample->sensor[1] = sample[-1].sensor[1];
|
|
|
|
|
}
|
2011-09-12 19:56:34 +00:00
|
|
|
|
return sample;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
|
void finish_sample(struct divecomputer *dc)
|
2011-09-12 19:56:34 +00:00
|
|
|
|
{
|
2012-11-24 02:51:27 +00:00
|
|
|
|
dc->samples++;
|
2011-09-12 19:56:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-09-03 20:36:25 +00:00
|
|
|
|
/*
|
|
|
|
|
* So when we re-calculate maxdepth and meandepth, we will
|
|
|
|
|
* not override the old numbers if they are close to the
|
|
|
|
|
* new ones.
|
|
|
|
|
*
|
|
|
|
|
* Why? Because a dive computer may well actually track the
|
2017-03-06 12:27:39 +00:00
|
|
|
|
* max. depth and mean depth at finer granularity than the
|
2011-09-03 20:36:25 +00:00
|
|
|
|
* samples it stores. So it's possible that the max and mean
|
|
|
|
|
* have been reported more correctly originally.
|
|
|
|
|
*
|
2011-09-04 01:48:39 +00:00
|
|
|
|
* Only if the values calculated from the samples are clearly
|
2011-09-03 20:36:25 +00:00
|
|
|
|
* different do we override the normal depth values.
|
|
|
|
|
*
|
|
|
|
|
* This considers 1m to be "clearly different". That's
|
|
|
|
|
* a totally random number.
|
|
|
|
|
*/
|
|
|
|
|
static void update_depth(depth_t *depth, int new)
|
|
|
|
|
{
|
2011-09-04 20:06:47 +00:00
|
|
|
|
if (new) {
|
|
|
|
|
int old = depth->mm;
|
2011-09-03 20:36:25 +00:00
|
|
|
|
|
2011-09-04 20:06:47 +00:00
|
|
|
|
if (abs(old - new) > 1000)
|
|
|
|
|
depth->mm = new;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_temperature(temperature_t *temperature, int new)
|
|
|
|
|
{
|
|
|
|
|
if (new) {
|
|
|
|
|
int old = temperature->mkelvin;
|
|
|
|
|
|
|
|
|
|
if (abs(old - new) > 1000)
|
|
|
|
|
temperature->mkelvin = new;
|
|
|
|
|
}
|
2011-09-03 20:36:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-02-24 19:39:51 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2014-08-04 14:36:07 +00:00
|
|
|
|
void fixup_dc_duration(struct divecomputer *dc)
|
2013-02-24 19:39:51 +00:00
|
|
|
|
{
|
|
|
|
|
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;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
depthtime += (time - lasttime) * (depth + lastdepth) / 2;
|
2013-02-24 19:39:51 +00:00
|
|
|
|
}
|
|
|
|
|
lastdepth = depth;
|
|
|
|
|
lasttime = time;
|
|
|
|
|
}
|
|
|
|
|
if (duration) {
|
|
|
|
|
dc->duration.seconds = duration;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
dc->meandepth.mm = (depthtime + duration / 2) / duration;
|
2013-02-24 19:39:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Fix per-cylinder SAC rate calculations when cylinder use isn't known
John Van Ostrand reports that when he dives using two cylinders using
sidemounts, the per-cylinder SAC rate display is very misleading.
What happens is that since the two cylinders are used together (but
without a manifold), John is alternating between the two but not
actually adding gas switches in the profile. As a result, the profile
looks like only one cylinder is used, even though clearly the other
cylinder gets breathed down too.
The per-cylinder SAC rate calculations would entirely ignore the
cylinder that didn't have gas switch events to it, and looking at the
info window it would look like John had a truly exceptional SAC rate.
But then in the general statistics panel that actually takes the whole
gas use into account, the very different real SAC rate would show up.
The basic issue is that if we don't have full use information for the
different cylinders, we would account the whole dive to just a partial
set. We did have a special case for this, but that special case only
really worked if the first cylinder truly was the only cylinder used.
This patch makes us see the difference between "only one cylinder was
used, and I can use the overall mean depth for it" and "more than one
cylinder was used, but I don't know what the mean depths might be".
Reported-by: John Van Ostrand <john@vanostrand.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-05 22:53:02 +00:00
|
|
|
|
/* Which cylinders had gas used? */
|
|
|
|
|
#define SOME_GAS 5000
|
|
|
|
|
static unsigned int get_cylinder_used(struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
unsigned int mask = 0;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
cylinder_t *cyl = dive->cylinder + i;
|
|
|
|
|
int start_mbar, end_mbar;
|
|
|
|
|
|
|
|
|
|
if (cylinder_nodata(cyl))
|
|
|
|
|
continue;
|
|
|
|
|
start_mbar = cyl->start.mbar ?: cyl->sample_start.mbar;
|
|
|
|
|
end_mbar = cyl->end.mbar ?: cyl->sample_end.mbar;
|
|
|
|
|
|
|
|
|
|
// More than 5 bar used? This matches statistics.c
|
|
|
|
|
// heuristics
|
|
|
|
|
if (start_mbar > end_mbar + SOME_GAS)
|
|
|
|
|
mask |= 1 << i;
|
|
|
|
|
}
|
|
|
|
|
return mask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Which cylinders do we know usage about? */
|
|
|
|
|
static unsigned int get_cylinder_known(struct dive *dive, struct divecomputer *dc)
|
|
|
|
|
{
|
|
|
|
|
unsigned int mask = 0;
|
|
|
|
|
struct event *ev;
|
|
|
|
|
|
|
|
|
|
/* We know about using the O2 cylinder in a CCR dive */
|
|
|
|
|
if (dc->divemode == CCR) {
|
|
|
|
|
int o2_cyl = get_cylinder_idx_by_use(dive, OXYGEN);
|
|
|
|
|
if (o2_cyl >= 0)
|
|
|
|
|
mask |= 1 << o2_cyl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* We know about the explicit first cylinder (or first) */
|
|
|
|
|
mask |= 1 << explicit_first_cylinder(dive, dc);
|
|
|
|
|
|
|
|
|
|
/* And we have possible switches to other gases */
|
|
|
|
|
ev = get_next_event(dc->events, "gaschange");
|
|
|
|
|
while (ev) {
|
|
|
|
|
int i = get_cylinder_index(dive, ev);
|
|
|
|
|
if (i >= 0)
|
|
|
|
|
mask |= 1 << i;
|
|
|
|
|
ev = get_next_event(ev->next, "gaschange");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mask;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-20 06:50:02 +00:00
|
|
|
|
void per_cylinder_mean_depth(struct dive *dive, struct divecomputer *dc, int *mean, int *duration)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
int depthtime[MAX_CYLINDERS] = { 0, };
|
2016-03-10 02:18:58 +00:00
|
|
|
|
uint32_t lasttime = 0;
|
|
|
|
|
int lastdepth = 0;
|
2013-11-20 06:50:02 +00:00
|
|
|
|
int idx = 0;
|
Fix per-cylinder SAC rate calculations when cylinder use isn't known
John Van Ostrand reports that when he dives using two cylinders using
sidemounts, the per-cylinder SAC rate display is very misleading.
What happens is that since the two cylinders are used together (but
without a manifold), John is alternating between the two but not
actually adding gas switches in the profile. As a result, the profile
looks like only one cylinder is used, even though clearly the other
cylinder gets breathed down too.
The per-cylinder SAC rate calculations would entirely ignore the
cylinder that didn't have gas switch events to it, and looking at the
info window it would look like John had a truly exceptional SAC rate.
But then in the general statistics panel that actually takes the whole
gas use into account, the very different real SAC rate would show up.
The basic issue is that if we don't have full use information for the
different cylinders, we would account the whole dive to just a partial
set. We did have a special case for this, but that special case only
really worked if the first cylinder truly was the only cylinder used.
This patch makes us see the difference between "only one cylinder was
used, and I can use the overall mean depth for it" and "more than one
cylinder was used, but I don't know what the mean depths might be".
Reported-by: John Van Ostrand <john@vanostrand.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-05 22:53:02 +00:00
|
|
|
|
unsigned int used_mask, known_mask;
|
2013-11-20 06:50:02 +00:00
|
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++)
|
|
|
|
|
mean[i] = duration[i] = 0;
|
2015-06-22 04:38:12 +00:00
|
|
|
|
if (!dc)
|
|
|
|
|
return;
|
Fix per-cylinder SAC rate calculations when cylinder use isn't known
John Van Ostrand reports that when he dives using two cylinders using
sidemounts, the per-cylinder SAC rate display is very misleading.
What happens is that since the two cylinders are used together (but
without a manifold), John is alternating between the two but not
actually adding gas switches in the profile. As a result, the profile
looks like only one cylinder is used, even though clearly the other
cylinder gets breathed down too.
The per-cylinder SAC rate calculations would entirely ignore the
cylinder that didn't have gas switch events to it, and looking at the
info window it would look like John had a truly exceptional SAC rate.
But then in the general statistics panel that actually takes the whole
gas use into account, the very different real SAC rate would show up.
The basic issue is that if we don't have full use information for the
different cylinders, we would account the whole dive to just a partial
set. We did have a special case for this, but that special case only
really worked if the first cylinder truly was the only cylinder used.
This patch makes us see the difference between "only one cylinder was
used, and I can use the overall mean depth for it" and "more than one
cylinder was used, but I don't know what the mean depths might be".
Reported-by: John Van Ostrand <john@vanostrand.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-05 22:53:02 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* There is no point in doing per-cylinder information
|
|
|
|
|
* if we don't actually know about the usage of all the
|
|
|
|
|
* used cylinders.
|
|
|
|
|
*/
|
|
|
|
|
used_mask = get_cylinder_used(dive);
|
|
|
|
|
known_mask = get_cylinder_known(dive, dc);
|
|
|
|
|
if (used_mask & ~known_mask) {
|
|
|
|
|
/*
|
|
|
|
|
* If we had more than one used cylinder, but
|
|
|
|
|
* do not know usage of them, we simply cannot
|
|
|
|
|
* account mean depth to them.
|
|
|
|
|
*
|
|
|
|
|
* The "x & (x-1)" test shows if it's not a pure
|
|
|
|
|
* power of two.
|
|
|
|
|
*/
|
|
|
|
|
if (used_mask & (used_mask-1))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* For a single cylinder, use the overall mean
|
|
|
|
|
* and duration
|
|
|
|
|
*/
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
if (used_mask & (1 << i)) {
|
|
|
|
|
mean[i] = dc->meandepth.mm;
|
|
|
|
|
duration[i] = dc->duration.seconds;
|
|
|
|
|
}
|
2014-11-17 00:09:59 +00:00
|
|
|
|
}
|
Fix per-cylinder SAC rate calculations when cylinder use isn't known
John Van Ostrand reports that when he dives using two cylinders using
sidemounts, the per-cylinder SAC rate display is very misleading.
What happens is that since the two cylinders are used together (but
without a manifold), John is alternating between the two but not
actually adding gas switches in the profile. As a result, the profile
looks like only one cylinder is used, even though clearly the other
cylinder gets breathed down too.
The per-cylinder SAC rate calculations would entirely ignore the
cylinder that didn't have gas switch events to it, and looking at the
info window it would look like John had a truly exceptional SAC rate.
But then in the general statistics panel that actually takes the whole
gas use into account, the very different real SAC rate would show up.
The basic issue is that if we don't have full use information for the
different cylinders, we would account the whole dive to just a partial
set. We did have a special case for this, but that special case only
really worked if the first cylinder truly was the only cylinder used.
This patch makes us see the difference between "only one cylinder was
used, and I can use the overall mean depth for it" and "more than one
cylinder was used, but I don't know what the mean depths might be".
Reported-by: John Van Ostrand <john@vanostrand.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-05 22:53:02 +00:00
|
|
|
|
|
2013-11-20 06:50:02 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
Make gas use statistics be coherent and more complete
The gas use logic in the dive statistics page is confused.
The SAC case had a special case for "unknown", but only for
the first gas. Other gases had the normal empty case.
Also, the logic was really odd - if you had gases that weren't used (or
pressures not known) intermixed with gases you *did* have pressure for,
the statistics got really confused.
The list of gases showed all gases that we know about during the dive,
but then the gas use and SAC-rate lists wouldn't necessarily match,
because the loops that computed those stopped after the first gas that
didn't have any pressure change.
To make things worse, the first cylinder was special-cased again, so it
all lined up for the single-cylinder case.
This makes all the cylinders act the same way, leaving unknown gas use
(and thus SAC) just empty for that gas.
It also fixes the SAC calculation case where we don't have real samples,
and the profile is a fake profile - possibly with gas changes in between
the fake points. We now make the SAC calculations match what we show -
which is admittedly not at all necessarily what the dive was, but at
least we're consistent.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-07-30 17:08:33 +00:00
|
|
|
|
if (!dc->samples)
|
2016-02-20 17:36:14 +00:00
|
|
|
|
dc = fake_dc(dc, false);
|
Fix per-cylinder SAC rate calculations when cylinder use isn't known
John Van Ostrand reports that when he dives using two cylinders using
sidemounts, the per-cylinder SAC rate display is very misleading.
What happens is that since the two cylinders are used together (but
without a manifold), John is alternating between the two but not
actually adding gas switches in the profile. As a result, the profile
looks like only one cylinder is used, even though clearly the other
cylinder gets breathed down too.
The per-cylinder SAC rate calculations would entirely ignore the
cylinder that didn't have gas switch events to it, and looking at the
info window it would look like John had a truly exceptional SAC rate.
But then in the general statistics panel that actually takes the whole
gas use into account, the very different real SAC rate would show up.
The basic issue is that if we don't have full use information for the
different cylinders, we would account the whole dive to just a partial
set. We did have a special case for this, but that special case only
really worked if the first cylinder truly was the only cylinder used.
This patch makes us see the difference between "only one cylinder was
used, and I can use the overall mean depth for it" and "more than one
cylinder was used, but I don't know what the mean depths might be".
Reported-by: John Van Ostrand <john@vanostrand.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-05 22:53:02 +00:00
|
|
|
|
struct event *ev = get_next_event(dc->events, "gaschange");
|
2013-11-20 06:50:02 +00:00
|
|
|
|
for (i = 0; i < dc->samples; i++) {
|
|
|
|
|
struct sample *sample = dc->sample + i;
|
2016-03-10 02:18:58 +00:00
|
|
|
|
uint32_t time = sample->time.seconds;
|
2013-11-20 06:50:02 +00:00
|
|
|
|
int depth = sample->depth.mm;
|
Make gas use statistics be coherent and more complete
The gas use logic in the dive statistics page is confused.
The SAC case had a special case for "unknown", but only for
the first gas. Other gases had the normal empty case.
Also, the logic was really odd - if you had gases that weren't used (or
pressures not known) intermixed with gases you *did* have pressure for,
the statistics got really confused.
The list of gases showed all gases that we know about during the dive,
but then the gas use and SAC-rate lists wouldn't necessarily match,
because the loops that computed those stopped after the first gas that
didn't have any pressure change.
To make things worse, the first cylinder was special-cased again, so it
all lined up for the single-cylinder case.
This makes all the cylinders act the same way, leaving unknown gas use
(and thus SAC) just empty for that gas.
It also fixes the SAC calculation case where we don't have real samples,
and the profile is a fake profile - possibly with gas changes in between
the fake points. We now make the SAC calculations match what we show -
which is admittedly not at all necessarily what the dive was, but at
least we're consistent.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-07-30 17:08:33 +00:00
|
|
|
|
|
|
|
|
|
/* Make sure to move the event past 'lasttime' */
|
|
|
|
|
while (ev && lasttime >= ev->time.seconds) {
|
2013-11-20 18:52:17 +00:00
|
|
|
|
idx = get_cylinder_index(dive, ev);
|
2013-11-20 06:50:02 +00:00
|
|
|
|
ev = get_next_event(ev->next, "gaschange");
|
|
|
|
|
}
|
Make gas use statistics be coherent and more complete
The gas use logic in the dive statistics page is confused.
The SAC case had a special case for "unknown", but only for
the first gas. Other gases had the normal empty case.
Also, the logic was really odd - if you had gases that weren't used (or
pressures not known) intermixed with gases you *did* have pressure for,
the statistics got really confused.
The list of gases showed all gases that we know about during the dive,
but then the gas use and SAC-rate lists wouldn't necessarily match,
because the loops that computed those stopped after the first gas that
didn't have any pressure change.
To make things worse, the first cylinder was special-cased again, so it
all lined up for the single-cylinder case.
This makes all the cylinders act the same way, leaving unknown gas use
(and thus SAC) just empty for that gas.
It also fixes the SAC calculation case where we don't have real samples,
and the profile is a fake profile - possibly with gas changes in between
the fake points. We now make the SAC calculations match what we show -
which is admittedly not at all necessarily what the dive was, but at
least we're consistent.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-07-30 17:08:33 +00:00
|
|
|
|
|
|
|
|
|
/* Do we need to fake a midway sample at an event? */
|
|
|
|
|
if (ev && time > ev->time.seconds) {
|
|
|
|
|
int newtime = ev->time.seconds;
|
|
|
|
|
int newdepth = interpolate(lastdepth, depth, newtime - lasttime, time - lasttime);
|
|
|
|
|
|
|
|
|
|
time = newtime;
|
|
|
|
|
depth = newdepth;
|
|
|
|
|
i--;
|
|
|
|
|
}
|
2013-11-20 06:50:02 +00:00
|
|
|
|
/* We ignore segments at the surface */
|
|
|
|
|
if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) {
|
|
|
|
|
duration[idx] += time - lasttime;
|
|
|
|
|
depthtime[idx] += (time - lasttime) * (depth + lastdepth) / 2;
|
|
|
|
|
}
|
|
|
|
|
lastdepth = depth;
|
|
|
|
|
lasttime = time;
|
|
|
|
|
}
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
if (duration[i])
|
|
|
|
|
mean[i] = (depthtime[i] + duration[i] / 2) / duration[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-09 04:10:47 +00:00
|
|
|
|
static void update_min_max_temperatures(struct dive *dive, temperature_t temperature)
|
2013-01-24 18:58:59 +00:00
|
|
|
|
{
|
2013-02-09 04:10:47 +00:00
|
|
|
|
if (temperature.mkelvin) {
|
|
|
|
|
if (!dive->maxtemp.mkelvin || temperature.mkelvin > dive->maxtemp.mkelvin)
|
|
|
|
|
dive->maxtemp = temperature;
|
|
|
|
|
if (!dive->mintemp.mkelvin || temperature.mkelvin < dive->mintemp.mkelvin)
|
|
|
|
|
dive->mintemp = temperature;
|
2013-01-24 18:58:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-25 23:23:16 +00:00
|
|
|
|
int gas_volume(cylinder_t *cyl, pressure_t p)
|
|
|
|
|
{
|
2016-02-24 20:14:53 +00:00
|
|
|
|
double bar = p.mbar / 1000.0;
|
|
|
|
|
double z_factor = gas_compressibility_factor(&cyl->gasmix, bar);
|
2017-03-08 06:41:41 +00:00
|
|
|
|
return lrint(cyl->type.size.mliter * bar_to_atm(bar) / z_factor);
|
2013-02-25 23:23:16 +00:00
|
|
|
|
}
|
2013-01-24 18:58:59 +00:00
|
|
|
|
|
2011-11-10 23:33:38 +00:00
|
|
|
|
/*
|
|
|
|
|
* If the cylinder tank pressures are within half a bar
|
|
|
|
|
* (about 8 PSI) of the sample pressures, we consider it
|
|
|
|
|
* to be a rounding error, and throw them away as redundant.
|
|
|
|
|
*/
|
|
|
|
|
static int same_rounded_pressure(pressure_t a, pressure_t b)
|
|
|
|
|
{
|
|
|
|
|
return abs(a.mbar - b.mbar) <= 500;
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-28 20:48:15 +00:00
|
|
|
|
/* Some dive computers (Cobalt) don't start the dive with cylinder 0 but explicitly
|
|
|
|
|
* tell us what the first gas is with a gas change event in the first sample.
|
|
|
|
|
* Sneakily we'll use a return value of 0 (or FALSE) when there is no explicit
|
|
|
|
|
* first cylinder - in which case cylinder 0 is indeed the first cylinder */
|
|
|
|
|
int explicit_first_cylinder(struct dive *dive, struct divecomputer *dc)
|
|
|
|
|
{
|
2015-06-22 04:38:12 +00:00
|
|
|
|
if (dc) {
|
|
|
|
|
struct event *ev = get_next_event(dc->events, "gaschange");
|
2017-02-04 13:26:04 +00:00
|
|
|
|
if (ev && ((dc->sample && ev->time.seconds == dc->sample[0].time.seconds) || ev->time.seconds <= 1))
|
2015-06-22 04:38:12 +00:00
|
|
|
|
return get_cylinder_index(dive, ev);
|
|
|
|
|
else if (dc->divemode == CCR)
|
|
|
|
|
return MAX(get_cylinder_idx_by_use(dive, DILUENT), 0);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
2014-10-28 20:48:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-02-07 16:31:16 +00:00
|
|
|
|
/* this gets called when the dive mode has changed (so OC vs. CC)
|
|
|
|
|
* there are two places we might have setpoints... events or in the samples
|
|
|
|
|
*/
|
2016-04-02 20:06:54 +00:00
|
|
|
|
void update_setpoint_events(struct dive *dive, struct divecomputer *dc)
|
2015-01-01 16:00:46 +00:00
|
|
|
|
{
|
2015-02-07 16:31:16 +00:00
|
|
|
|
struct event *ev;
|
2015-01-01 16:00:46 +00:00
|
|
|
|
int new_setpoint = 0;
|
|
|
|
|
|
2015-01-10 23:01:15 +00:00
|
|
|
|
if (dc->divemode == CCR)
|
2015-02-07 16:31:16 +00:00
|
|
|
|
new_setpoint = prefs.defaultsetpoint;
|
|
|
|
|
|
2015-02-10 23:19:01 +00:00
|
|
|
|
if (dc->divemode == OC &&
|
|
|
|
|
(same_string(dc->model, "Shearwater Predator") ||
|
2015-10-16 12:25:56 +00:00
|
|
|
|
same_string(dc->model, "Shearwater Petrel") ||
|
|
|
|
|
same_string(dc->model, "Shearwater Nerd"))) {
|
2015-02-07 16:31:16 +00:00
|
|
|
|
// make sure there's no setpoint in the samples
|
|
|
|
|
// this is an irreversible change - so switching a dive to OC
|
|
|
|
|
// by mistake when it's actually CCR is _bad_
|
2015-02-10 23:19:01 +00:00
|
|
|
|
// So we make sure, this comes from a Predator or Petrel and we only remove
|
2015-02-07 20:02:02 +00:00
|
|
|
|
// pO2 values we would have computed anyway.
|
|
|
|
|
struct event *ev = get_next_event(dc->events, "gaschange");
|
2016-04-02 20:06:54 +00:00
|
|
|
|
struct gasmix *gasmix = get_gasmix_from_event(dive, ev);
|
2015-02-07 20:02:02 +00:00
|
|
|
|
struct event *next = get_next_event(ev, "gaschange");
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < dc->samples; i++) {
|
|
|
|
|
struct gas_pressures pressures;
|
|
|
|
|
if (next && dc->sample[i].time.seconds >= next->time.seconds) {
|
|
|
|
|
ev = next;
|
2016-04-02 20:06:54 +00:00
|
|
|
|
gasmix = get_gasmix_from_event(dive, ev);
|
2015-02-07 20:02:02 +00:00
|
|
|
|
next = get_next_event(ev, "gaschange");
|
|
|
|
|
}
|
|
|
|
|
fill_pressures(&pressures, calculate_depth_to_mbar(dc->sample[i].depth.mm, dc->surface_pressure, 0), gasmix ,0, OC);
|
2015-12-27 22:33:16 +00:00
|
|
|
|
if (abs(dc->sample[i].setpoint.mbar - (int)(1000 * pressures.o2)) <= 50)
|
2015-02-07 20:02:02 +00:00
|
|
|
|
dc->sample[i].setpoint.mbar = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-02-07 16:31:16 +00:00
|
|
|
|
|
|
|
|
|
// an "SP change" event at t=0 is currently our marker for OC vs CCR
|
|
|
|
|
// this will need to change to a saner setup, but for now we can just
|
|
|
|
|
// check if such an event is there and adjust it, or add that event
|
|
|
|
|
ev = get_next_event(dc->events, "SP change");
|
|
|
|
|
if (ev && ev->time.seconds == 0) {
|
|
|
|
|
ev->value = new_setpoint;
|
|
|
|
|
} else {
|
2015-01-08 13:42:07 +00:00
|
|
|
|
if (!add_event(dc, 0, SAMPLE_EVENT_PO2, 0, new_setpoint, "SP change"))
|
2015-02-07 16:31:16 +00:00
|
|
|
|
fprintf(stderr, "Could not add setpoint change event\n");
|
2015-01-08 13:42:07 +00:00
|
|
|
|
}
|
2015-01-01 16:00:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-05-25 16:01:16 +00:00
|
|
|
|
void sanitize_gasmix(struct gasmix *mix)
|
2011-12-30 21:09:17 +00:00
|
|
|
|
{
|
|
|
|
|
unsigned int o2, he;
|
|
|
|
|
|
|
|
|
|
o2 = mix->o2.permille;
|
|
|
|
|
he = mix->he.permille;
|
|
|
|
|
|
|
|
|
|
/* Regular air: leave empty */
|
|
|
|
|
if (!he) {
|
|
|
|
|
if (!o2)
|
|
|
|
|
return;
|
2013-01-14 22:53:38 +00:00
|
|
|
|
/* 20.8% to 21% O2 is just air */
|
2014-06-02 03:56:29 +00:00
|
|
|
|
if (gasmix_is_air(mix)) {
|
2011-12-30 21:09:17 +00:00
|
|
|
|
mix->o2.permille = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Sane mix? */
|
2014-02-28 04:09:57 +00:00
|
|
|
|
if (o2 <= 1000 && he <= 1000 && o2 + he <= 1000)
|
2011-12-30 21:09:17 +00:00
|
|
|
|
return;
|
2012-09-18 23:51:48 +00:00
|
|
|
|
fprintf(stderr, "Odd gasmix: %u O2 %u He\n", o2, he);
|
2011-12-30 21:09:17 +00:00
|
|
|
|
memset(mix, 0, sizeof(*mix));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* See if the size/workingpressure looks like some standard cylinder
|
|
|
|
|
* size, eg "AL80".
|
2016-02-24 19:31:03 +00:00
|
|
|
|
*
|
|
|
|
|
* NOTE! We don't take compressibility into account when naming
|
|
|
|
|
* cylinders. That makes a certain amount of sense, since the
|
|
|
|
|
* cylinder name is independent from the gasmix, and different
|
|
|
|
|
* gasmixes have different compressibility.
|
2011-12-30 21:09:17 +00:00
|
|
|
|
*/
|
|
|
|
|
static void match_standard_cylinder(cylinder_type_t *type)
|
|
|
|
|
{
|
2016-02-24 19:31:03 +00:00
|
|
|
|
double cuft, bar;
|
2011-12-30 21:09:17 +00:00
|
|
|
|
int psi, len;
|
|
|
|
|
const char *fmt;
|
2013-03-04 01:53:43 +00:00
|
|
|
|
char buffer[40], *p;
|
2011-12-30 21:09:17 +00:00
|
|
|
|
|
|
|
|
|
/* Do we already have a cylinder description? */
|
|
|
|
|
if (type->description)
|
|
|
|
|
return;
|
|
|
|
|
|
2016-02-24 19:31:03 +00:00
|
|
|
|
bar = type->workingpressure.mbar / 1000.0;
|
2011-12-30 21:09:17 +00:00
|
|
|
|
cuft = ml_to_cuft(type->size.mliter);
|
2016-02-24 19:31:03 +00:00
|
|
|
|
cuft *= bar_to_atm(bar);
|
2011-12-30 21:09:17 +00:00
|
|
|
|
psi = to_PSI(type->workingpressure);
|
|
|
|
|
|
|
|
|
|
switch (psi) {
|
2014-02-28 04:09:57 +00:00
|
|
|
|
case 2300 ... 2500: /* 2400 psi: LP tank */
|
2011-12-30 21:09:17 +00:00
|
|
|
|
fmt = "LP%d";
|
|
|
|
|
break;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
case 2600 ... 2700: /* 2640 psi: LP+10% */
|
2011-12-30 21:09:17 +00:00
|
|
|
|
fmt = "LP%d";
|
|
|
|
|
break;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
case 2900 ... 3100: /* 3000 psi: ALx tank */
|
2011-12-30 21:09:17 +00:00
|
|
|
|
fmt = "AL%d";
|
|
|
|
|
break;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
case 3400 ... 3500: /* 3442 psi: HP tank */
|
2011-12-30 21:09:17 +00:00
|
|
|
|
fmt = "HP%d";
|
|
|
|
|
break;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
case 3700 ... 3850: /* HP+10% */
|
2011-12-30 21:09:17 +00:00
|
|
|
|
fmt = "HP%d+";
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-03-08 06:41:41 +00:00
|
|
|
|
len = snprintf(buffer, sizeof(buffer), fmt, (int)lrint(cuft));
|
2014-02-28 04:09:57 +00:00
|
|
|
|
p = malloc(len + 1);
|
2011-12-30 21:09:17 +00:00
|
|
|
|
if (!p)
|
|
|
|
|
return;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
memcpy(p, buffer, len + 1);
|
2011-12-30 21:09:17 +00:00
|
|
|
|
type->description = p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* There are two ways to give cylinder size information:
|
|
|
|
|
* - total amount of gas in cuft (depends on working pressure and physical size)
|
|
|
|
|
* - physical size
|
|
|
|
|
*
|
|
|
|
|
* where "physical size" is the one that actually matters and is sane.
|
|
|
|
|
*
|
|
|
|
|
* We internally use physical size only. But we save the workingpressure
|
|
|
|
|
* so that we can do the conversion if required.
|
|
|
|
|
*/
|
|
|
|
|
static void sanitize_cylinder_type(cylinder_type_t *type)
|
|
|
|
|
{
|
2013-02-25 23:23:16 +00:00
|
|
|
|
double volume_of_air, volume;
|
2011-12-30 21:09:17 +00:00
|
|
|
|
|
|
|
|
|
/* If we have no working pressure, it had *better* be just a physical size! */
|
|
|
|
|
if (!type->workingpressure.mbar)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/* No size either? Nothing to go on */
|
|
|
|
|
if (!type->size.mliter)
|
|
|
|
|
return;
|
|
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
|
if (xml_parsing_units.volume == CUFT) {
|
2016-02-24 19:31:03 +00:00
|
|
|
|
double bar = type->workingpressure.mbar / 1000.0;
|
2011-12-30 21:09:17 +00:00
|
|
|
|
/* confusing - we don't really start from ml but millicuft !*/
|
|
|
|
|
volume_of_air = cuft_to_l(type->size.mliter);
|
2016-02-24 19:31:03 +00:00
|
|
|
|
/* milliliters at 1 atm: not corrected for compressibility! */
|
|
|
|
|
volume = volume_of_air / bar_to_atm(bar);
|
2017-03-08 06:41:41 +00:00
|
|
|
|
type->size.mliter = lrint(volume);
|
2011-12-30 21:09:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Ok, we have both size and pressure: try to match a description */
|
|
|
|
|
match_standard_cylinder(type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void sanitize_cylinder_info(struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
sanitize_gasmix(&dive->cylinder[i].gasmix);
|
|
|
|
|
sanitize_cylinder_type(&dive->cylinder[i].type);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-10 10:40:35 +00:00
|
|
|
|
/* some events should never be thrown away */
|
2013-10-05 07:29:09 +00:00
|
|
|
|
static bool is_potentially_redundant(struct event *event)
|
2012-11-10 10:40:35 +00:00
|
|
|
|
{
|
|
|
|
|
if (!strcmp(event->name, "gaschange"))
|
2014-01-15 18:54:41 +00:00
|
|
|
|
return false;
|
2012-11-10 19:02:21 +00:00
|
|
|
|
if (!strcmp(event->name, "bookmark"))
|
2014-01-15 18:54:41 +00:00
|
|
|
|
return false;
|
2012-11-10 19:02:21 +00:00
|
|
|
|
if (!strcmp(event->name, "heading"))
|
2014-01-15 18:54:41 +00:00
|
|
|
|
return false;
|
|
|
|
|
return true;
|
2012-11-10 10:40:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* match just by name - we compare the details in the code that uses this helper */
|
2012-11-24 02:51:27 +00:00
|
|
|
|
static struct event *find_previous_event(struct divecomputer *dc, struct event *event)
|
2012-11-10 10:40:35 +00:00
|
|
|
|
{
|
2012-11-24 02:51:27 +00:00
|
|
|
|
struct event *ev = dc->events;
|
2012-11-10 10:40:35 +00:00
|
|
|
|
struct event *previous = NULL;
|
|
|
|
|
|
2015-06-22 05:07:10 +00:00
|
|
|
|
if (same_string(event->name, ""))
|
2012-11-10 10:40:35 +00:00
|
|
|
|
return NULL;
|
|
|
|
|
while (ev && ev != event) {
|
2015-06-22 05:07:10 +00:00
|
|
|
|
if (same_string(ev->name, event->name))
|
2012-11-10 10:40:35 +00:00
|
|
|
|
previous = ev;
|
|
|
|
|
ev = ev->next;
|
|
|
|
|
}
|
|
|
|
|
return previous;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-09 00:15:18 +00:00
|
|
|
|
static void fixup_surface_pressure(struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
struct divecomputer *dc;
|
2013-02-09 13:45:58 +00:00
|
|
|
|
int sum = 0, nr = 0;
|
2013-02-09 00:15:18 +00:00
|
|
|
|
|
2014-10-11 11:25:52 +00:00
|
|
|
|
for_each_dc (dive, dc) {
|
2013-02-09 00:15:18 +00:00
|
|
|
|
if (dc->surface_pressure.mbar) {
|
|
|
|
|
sum += dc->surface_pressure.mbar;
|
|
|
|
|
nr++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (nr)
|
2014-02-28 04:09:57 +00:00
|
|
|
|
dive->surface_pressure.mbar = (sum + nr / 2) / nr;
|
2013-02-09 00:15:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void fixup_water_salinity(struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
struct divecomputer *dc;
|
2013-02-09 13:45:58 +00:00
|
|
|
|
int sum = 0, nr = 0;
|
2013-02-09 00:15:18 +00:00
|
|
|
|
|
2014-10-11 11:25:52 +00:00
|
|
|
|
for_each_dc (dive, dc) {
|
2013-02-09 00:15:18 +00:00
|
|
|
|
if (dc->salinity) {
|
2016-02-26 14:16:36 +00:00
|
|
|
|
if (dc->salinity < 500)
|
|
|
|
|
dc->salinity += FRESHWATER_SALINITY;
|
2013-02-09 00:15:18 +00:00
|
|
|
|
sum += dc->salinity;
|
|
|
|
|
nr++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (nr)
|
2014-02-28 04:09:57 +00:00
|
|
|
|
dive->salinity = (sum + nr / 2) / nr;
|
2013-02-09 00:15:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-02-09 14:50:53 +00:00
|
|
|
|
static void fixup_meandepth(struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
struct divecomputer *dc;
|
|
|
|
|
int sum = 0, nr = 0;
|
|
|
|
|
|
2014-10-11 11:25:52 +00:00
|
|
|
|
for_each_dc (dive, dc) {
|
2013-02-09 14:50:53 +00:00
|
|
|
|
if (dc->meandepth.mm) {
|
|
|
|
|
sum += dc->meandepth.mm;
|
|
|
|
|
nr++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (nr)
|
|
|
|
|
dive->meandepth.mm = (sum + nr / 2) / nr;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-09 15:12:30 +00:00
|
|
|
|
static void fixup_duration(struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
struct divecomputer *dc;
|
2014-06-04 00:06:59 +00:00
|
|
|
|
unsigned int duration = 0;
|
2013-02-09 15:12:30 +00:00
|
|
|
|
|
2014-10-11 11:25:52 +00:00
|
|
|
|
for_each_dc (dive, dc)
|
2014-03-03 21:25:55 +00:00
|
|
|
|
duration = MAX(duration, dc->duration.seconds);
|
2013-02-09 15:12:30 +00:00
|
|
|
|
|
|
|
|
|
dive->duration.seconds = duration;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-29 20:05:21 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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(struct divecomputer *dc)
|
2013-02-09 15:41:15 +00:00
|
|
|
|
{
|
|
|
|
|
int sum = 0, nr = 0;
|
|
|
|
|
|
2013-11-29 20:05:21 +00:00
|
|
|
|
do {
|
2013-02-09 15:41:15 +00:00
|
|
|
|
if (dc->watertemp.mkelvin) {
|
|
|
|
|
sum += dc->watertemp.mkelvin;
|
|
|
|
|
nr++;
|
|
|
|
|
}
|
2013-11-29 20:05:21 +00:00
|
|
|
|
} 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);
|
2013-02-09 15:41:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-02-14 23:18:48 +00:00
|
|
|
|
/*
|
|
|
|
|
* What do the dive computers say the air temperature is?
|
|
|
|
|
*/
|
|
|
|
|
unsigned int dc_airtemp(struct divecomputer *dc)
|
2013-02-09 15:41:15 +00:00
|
|
|
|
{
|
|
|
|
|
int sum = 0, nr = 0;
|
|
|
|
|
|
2013-02-14 23:18:48 +00:00
|
|
|
|
do {
|
2013-02-09 15:41:15 +00:00
|
|
|
|
if (dc->airtemp.mkelvin) {
|
|
|
|
|
sum += dc->airtemp.mkelvin;
|
|
|
|
|
nr++;
|
|
|
|
|
}
|
2013-02-14 23:18:48 +00:00
|
|
|
|
} 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)
|
|
|
|
|
dive->airtemp.mkelvin = dc_airtemp(&dive->dc);
|
2013-02-09 15:41:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-02-14 17:44:18 +00:00
|
|
|
|
/* zero out the airtemp in the dive structure if it was just created by
|
|
|
|
|
* running fixup on the dive. keep it if it had been edited by hand */
|
|
|
|
|
static void un_fixup_airtemp(struct dive *a)
|
|
|
|
|
{
|
2013-02-14 23:18:48 +00:00
|
|
|
|
if (a->airtemp.mkelvin && a->airtemp.mkelvin == dc_airtemp(&a->dc))
|
2013-02-14 17:44:18 +00:00
|
|
|
|
a->airtemp.mkelvin = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-09 00:35:39 +00:00
|
|
|
|
/*
|
|
|
|
|
* events are stored as a linked list, so the concept of
|
|
|
|
|
* "consecutive, identical events" is somewhat hard to
|
|
|
|
|
* implement correctly (especially given that on some dive
|
|
|
|
|
* computers events are asynchronous, so they can come in
|
|
|
|
|
* between what would be the non-constant sample rate).
|
|
|
|
|
*
|
|
|
|
|
* So what we do is that we throw away clearly redundant
|
|
|
|
|
* events that are fewer than 61 seconds apart (assuming there
|
|
|
|
|
* is no dive computer with a sample rate of more than 60
|
|
|
|
|
* seconds... that would be pretty pointless to plot the
|
|
|
|
|
* profile with)
|
|
|
|
|
*
|
|
|
|
|
* We first only mark the events for deletion so that we
|
|
|
|
|
* still know when the previous event happened.
|
|
|
|
|
*/
|
|
|
|
|
static void fixup_dc_events(struct divecomputer *dc)
|
2011-09-03 20:19:26 +00:00
|
|
|
|
{
|
2013-02-09 00:35:39 +00:00
|
|
|
|
struct event *event;
|
|
|
|
|
|
|
|
|
|
event = dc->events;
|
|
|
|
|
while (event) {
|
|
|
|
|
struct event *prev;
|
|
|
|
|
if (is_potentially_redundant(event)) {
|
|
|
|
|
prev = find_previous_event(dc, event);
|
|
|
|
|
if (prev && prev->value == event->value &&
|
|
|
|
|
prev->flags == event->flags &&
|
|
|
|
|
event->time.seconds - prev->time.seconds < 61)
|
2014-01-15 18:54:41 +00:00
|
|
|
|
event->deleted = true;
|
2013-02-09 00:35:39 +00:00
|
|
|
|
}
|
|
|
|
|
event = event->next;
|
|
|
|
|
}
|
|
|
|
|
event = dc->events;
|
|
|
|
|
while (event) {
|
|
|
|
|
if (event->next && event->next->deleted) {
|
|
|
|
|
struct event *nextnext = event->next->next;
|
|
|
|
|
free(event->next);
|
|
|
|
|
event->next = nextnext;
|
|
|
|
|
} else {
|
|
|
|
|
event = event->next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-25 03:02:08 +00:00
|
|
|
|
static int interpolate_depth(struct divecomputer *dc, int idx, int lastdepth, int lasttime, int now)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
int nextdepth = lastdepth;
|
|
|
|
|
int nexttime = now;
|
|
|
|
|
|
|
|
|
|
for (i = idx+1; i < dc->samples; i++) {
|
|
|
|
|
struct sample *sample = dc->sample + i;
|
|
|
|
|
if (sample->depth.mm < 0)
|
|
|
|
|
continue;
|
|
|
|
|
nextdepth = sample->depth.mm;
|
|
|
|
|
nexttime = sample->time.seconds;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return interpolate(lastdepth, nextdepth, now-lasttime, nexttime-lasttime);
|
|
|
|
|
}
|
|
|
|
|
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
static void fixup_dc_depths(struct dive *dive, struct divecomputer *dc)
|
2013-02-09 00:35:39 +00:00
|
|
|
|
{
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
int i;
|
2013-02-26 19:15:06 +00:00
|
|
|
|
int maxdepth = dc->maxdepth.mm;
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
int lasttime = 0, lastdepth = 0;
|
2014-10-28 20:48:15 +00:00
|
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
|
for (i = 0; i < dc->samples; i++) {
|
|
|
|
|
struct sample *sample = dc->sample + i;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
int time = sample->time.seconds;
|
|
|
|
|
int depth = sample->depth.mm;
|
2014-10-28 20:48:15 +00:00
|
|
|
|
|
2015-10-25 03:02:08 +00:00
|
|
|
|
if (depth < 0) {
|
|
|
|
|
depth = interpolate_depth(dc, i, lastdepth, lasttime, time);
|
|
|
|
|
sample->depth.mm = depth;
|
|
|
|
|
}
|
|
|
|
|
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
if (depth > SURFACE_THRESHOLD) {
|
|
|
|
|
if (depth > maxdepth)
|
|
|
|
|
maxdepth = depth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastdepth = depth;
|
|
|
|
|
lasttime = time;
|
|
|
|
|
if (sample->cns > dive->maxcns)
|
|
|
|
|
dive->maxcns = sample->cns;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update_depth(&dc->maxdepth, maxdepth);
|
|
|
|
|
if (maxdepth > dive->maxdepth.mm)
|
|
|
|
|
dive->maxdepth.mm = maxdepth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void fixup_dc_temp(struct dive *dive, struct divecomputer *dc)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
int mintemp = 0, lasttemp = 0;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < dc->samples; i++) {
|
|
|
|
|
struct sample *sample = dc->sample + i;
|
|
|
|
|
int temp = sample->temperature.mkelvin;
|
|
|
|
|
|
|
|
|
|
if (temp) {
|
|
|
|
|
/*
|
|
|
|
|
* If we have consecutive identical
|
|
|
|
|
* temperature readings, throw away
|
|
|
|
|
* the redundant ones.
|
|
|
|
|
*/
|
|
|
|
|
if (lasttemp == temp)
|
|
|
|
|
sample->temperature.mkelvin = 0;
|
|
|
|
|
else
|
|
|
|
|
lasttemp = temp;
|
|
|
|
|
|
|
|
|
|
if (!mintemp || temp < mintemp)
|
|
|
|
|
mintemp = temp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update_min_max_temperatures(dive, sample->temperature);
|
|
|
|
|
}
|
|
|
|
|
update_temperature(&dc->watertemp, mintemp);
|
|
|
|
|
update_min_max_temperatures(dive, dc->watertemp);
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-02 12:28:03 +00:00
|
|
|
|
/* Remove redundant pressure information */
|
2016-04-05 14:40:32 +00:00
|
|
|
|
static void simplify_dc_pressures(struct divecomputer *dc)
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
{
|
2016-04-02 12:28:03 +00:00
|
|
|
|
int i;
|
Start cleaning up sensor indexing for multiple sensors
This is a very timid start at making us actually use multiple sensors
without the magical special case for just CCR oxygen tracking.
It mainly does:
- turn the "sample->sensor" index into an array of two indexes, to
match the pressures themselves.
- get rid of dive->{oxygen_cylinder_index,diluent_cylinder_index},
since a CCR dive should now simply set the sample->sensor[] indices
correctly instead.
- in a couple of places, start actually looping over the sensors rather
than special-case the O2 case (although often the small "loops" are
just unrolled, since it's just two cases.
but in many cases we still end up only covering the zero sensor case,
because the CCR O2 sensor code coverage was fairly limited.
It's entirely possible (even likely) that this migth break some existing
case: it tries to be a fairly direct ("stupid") translation of the old
code, but unlike the preparatory patch this does actually does change
some semantics.
For example, right now the git loader code assumes that if the git save
data contains a o2pressure entry, it just hardcodes the O2 sensor index
to 1.
In fact, one issue is going to simply be that our file formats do not
have that multiple sensor format, but instead had very clearly encoded
things as being the CCR O2 pressure sensor.
But this is hopefully close to usable, and I will need feedback (and
maybe test cases) from people who have existing CCR dives with pressure
data.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-21 02:49:45 +00:00
|
|
|
|
int lastindex[2] = { -1, -1 };
|
|
|
|
|
int lastpressure[2] = { 0 };
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
|
|
|
|
|
for (i = 0; i < dc->samples; i++) {
|
Start cleaning up sensor indexing for multiple sensors
This is a very timid start at making us actually use multiple sensors
without the magical special case for just CCR oxygen tracking.
It mainly does:
- turn the "sample->sensor" index into an array of two indexes, to
match the pressures themselves.
- get rid of dive->{oxygen_cylinder_index,diluent_cylinder_index},
since a CCR dive should now simply set the sample->sensor[] indices
correctly instead.
- in a couple of places, start actually looping over the sensors rather
than special-case the O2 case (although often the small "loops" are
just unrolled, since it's just two cases.
but in many cases we still end up only covering the zero sensor case,
because the CCR O2 sensor code coverage was fairly limited.
It's entirely possible (even likely) that this migth break some existing
case: it tries to be a fairly direct ("stupid") translation of the old
code, but unlike the preparatory patch this does actually does change
some semantics.
For example, right now the git loader code assumes that if the git save
data contains a o2pressure entry, it just hardcodes the O2 sensor index
to 1.
In fact, one issue is going to simply be that our file formats do not
have that multiple sensor format, but instead had very clearly encoded
things as being the CCR O2 pressure sensor.
But this is hopefully close to usable, and I will need feedback (and
maybe test cases) from people who have existing CCR dives with pressure
data.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-21 02:49:45 +00:00
|
|
|
|
int j;
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
struct sample *sample = dc->sample + i;
|
Start cleaning up sensor indexing for multiple sensors
This is a very timid start at making us actually use multiple sensors
without the magical special case for just CCR oxygen tracking.
It mainly does:
- turn the "sample->sensor" index into an array of two indexes, to
match the pressures themselves.
- get rid of dive->{oxygen_cylinder_index,diluent_cylinder_index},
since a CCR dive should now simply set the sample->sensor[] indices
correctly instead.
- in a couple of places, start actually looping over the sensors rather
than special-case the O2 case (although often the small "loops" are
just unrolled, since it's just two cases.
but in many cases we still end up only covering the zero sensor case,
because the CCR O2 sensor code coverage was fairly limited.
It's entirely possible (even likely) that this migth break some existing
case: it tries to be a fairly direct ("stupid") translation of the old
code, but unlike the preparatory patch this does actually does change
some semantics.
For example, right now the git loader code assumes that if the git save
data contains a o2pressure entry, it just hardcodes the O2 sensor index
to 1.
In fact, one issue is going to simply be that our file formats do not
have that multiple sensor format, but instead had very clearly encoded
things as being the CCR O2 pressure sensor.
But this is hopefully close to usable, and I will need feedback (and
maybe test cases) from people who have existing CCR dives with pressure
data.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-21 02:49:45 +00:00
|
|
|
|
|
|
|
|
|
for (j = 0; j < 2; j++) {
|
|
|
|
|
int pressure = sample->pressure[j].mbar;
|
|
|
|
|
int index = sample->sensor[j];
|
|
|
|
|
|
|
|
|
|
if (index == lastindex[j]) {
|
|
|
|
|
/* Remove duplicate redundant pressure information */
|
|
|
|
|
if (pressure == lastpressure[j])
|
|
|
|
|
sample->pressure[j].mbar = 0;
|
|
|
|
|
}
|
|
|
|
|
lastindex[j] = index;
|
|
|
|
|
lastpressure[j] = pressure;
|
2011-11-19 12:09:14 +00:00
|
|
|
|
}
|
2011-09-03 20:19:26 +00:00
|
|
|
|
}
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
Fix cylinder end pressure fixup from samples
This bug admittedly hits almost nobody, but if you had multiple cylinder
pressure sensors on the same cylinder (attached to multiple dive
computers, of course), we would take the beginning pressure from the
first dive computer, and the ending pressure from the last dive
computer.
That came about because we'd just walk all the dive computer samples in
order, and the first time we see a relevant sample and we don't have a
beginning pressure, we'd take that pressure. So the beginning pressure
was from the first dive computer, and once we'd seen a valid beginning
pressure, that would never change.
But as we're walking along, we'd continue to update the ending pressure
from the last relevant sample we see, which means that as we go on to
look at the other dive computers, we'd continue to update the ending
pressure with data from them.
And mixing beginning/ending pressures from two different sensors just
does not make sense.
This changes the logic to be the same for beginning and ending
pressures: we only update it once, with the first relevant sample we
see. But we walk the samples twice: forwards from the beginning to
find the first beginning pressure, and backwards from the end to find
the ending pressure.
That means that as we move on to the second dive computer, we've now
filled in the ending pressure from the first one, and will no longer
update it any more.
NOTE! We don't stop scanning the samples (or the dive computers) just
because we've found a valid pressure value. We'll always walk all the
samples because there might be multiple different cylinders that get
pressure data from different samples (and different dive computers).
We could have some early-out logic when we've filled in all relevant
cylinders, but since this just runs once per dive it's not worth it.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 20:23:55 +00:00
|
|
|
|
/* FIXME! sensor -> cylinder mapping? */
|
|
|
|
|
static void fixup_start_pressure(struct dive *dive, int idx, pressure_t p)
|
|
|
|
|
{
|
|
|
|
|
if (idx >= 0 && idx < MAX_CYLINDERS) {
|
|
|
|
|
cylinder_t *cyl = dive->cylinder + idx;
|
|
|
|
|
if (p.mbar && !cyl->sample_start.mbar)
|
|
|
|
|
cyl->sample_start = p;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void fixup_end_pressure(struct dive *dive, int idx, pressure_t p)
|
|
|
|
|
{
|
|
|
|
|
if (idx >= 0 && idx < MAX_CYLINDERS) {
|
|
|
|
|
cylinder_t *cyl = dive->cylinder + idx;
|
|
|
|
|
if (p.mbar && !cyl->sample_end.mbar)
|
|
|
|
|
cyl->sample_end = p;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
/*
|
|
|
|
|
* Check the cylinder pressure sample information and fill in the
|
|
|
|
|
* overall cylinder pressures from those.
|
Fix cylinder end pressure fixup from samples
This bug admittedly hits almost nobody, but if you had multiple cylinder
pressure sensors on the same cylinder (attached to multiple dive
computers, of course), we would take the beginning pressure from the
first dive computer, and the ending pressure from the last dive
computer.
That came about because we'd just walk all the dive computer samples in
order, and the first time we see a relevant sample and we don't have a
beginning pressure, we'd take that pressure. So the beginning pressure
was from the first dive computer, and once we'd seen a valid beginning
pressure, that would never change.
But as we're walking along, we'd continue to update the ending pressure
from the last relevant sample we see, which means that as we go on to
look at the other dive computers, we'd continue to update the ending
pressure with data from them.
And mixing beginning/ending pressures from two different sensors just
does not make sense.
This changes the logic to be the same for beginning and ending
pressures: we only update it once, with the first relevant sample we
see. But we walk the samples twice: forwards from the beginning to
find the first beginning pressure, and backwards from the end to find
the ending pressure.
That means that as we move on to the second dive computer, we've now
filled in the ending pressure from the first one, and will no longer
update it any more.
NOTE! We don't stop scanning the samples (or the dive computers) just
because we've found a valid pressure value. We'll always walk all the
samples because there might be multiple different cylinders that get
pressure data from different samples (and different dive computers).
We could have some early-out logic when we've filled in all relevant
cylinders, but since this just runs once per dive it's not worth it.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 20:23:55 +00:00
|
|
|
|
*
|
|
|
|
|
* We ignore surface samples for tank pressure information.
|
|
|
|
|
*
|
|
|
|
|
* At the beginning of the dive, let the cylinder cool down
|
|
|
|
|
* if the diver starts off at the surface. And at the end
|
|
|
|
|
* of the dive, there may be surface pressures where the
|
|
|
|
|
* diver has already turned off the air supply (especially
|
|
|
|
|
* for computers like the Uemis Zurich that end up saving
|
|
|
|
|
* quite a bit of samples after the dive has ended).
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
*/
|
|
|
|
|
static void fixup_dive_pressures(struct dive *dive, struct divecomputer *dc)
|
|
|
|
|
{
|
Start cleaning up sensor indexing for multiple sensors
This is a very timid start at making us actually use multiple sensors
without the magical special case for just CCR oxygen tracking.
It mainly does:
- turn the "sample->sensor" index into an array of two indexes, to
match the pressures themselves.
- get rid of dive->{oxygen_cylinder_index,diluent_cylinder_index},
since a CCR dive should now simply set the sample->sensor[] indices
correctly instead.
- in a couple of places, start actually looping over the sensors rather
than special-case the O2 case (although often the small "loops" are
just unrolled, since it's just two cases.
but in many cases we still end up only covering the zero sensor case,
because the CCR O2 sensor code coverage was fairly limited.
It's entirely possible (even likely) that this migth break some existing
case: it tries to be a fairly direct ("stupid") translation of the old
code, but unlike the preparatory patch this does actually does change
some semantics.
For example, right now the git loader code assumes that if the git save
data contains a o2pressure entry, it just hardcodes the O2 sensor index
to 1.
In fact, one issue is going to simply be that our file formats do not
have that multiple sensor format, but instead had very clearly encoded
things as being the CCR O2 pressure sensor.
But this is hopefully close to usable, and I will need feedback (and
maybe test cases) from people who have existing CCR dives with pressure
data.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-21 02:49:45 +00:00
|
|
|
|
int i;
|
Fix cylinder end pressure fixup from samples
This bug admittedly hits almost nobody, but if you had multiple cylinder
pressure sensors on the same cylinder (attached to multiple dive
computers, of course), we would take the beginning pressure from the
first dive computer, and the ending pressure from the last dive
computer.
That came about because we'd just walk all the dive computer samples in
order, and the first time we see a relevant sample and we don't have a
beginning pressure, we'd take that pressure. So the beginning pressure
was from the first dive computer, and once we'd seen a valid beginning
pressure, that would never change.
But as we're walking along, we'd continue to update the ending pressure
from the last relevant sample we see, which means that as we go on to
look at the other dive computers, we'd continue to update the ending
pressure with data from them.
And mixing beginning/ending pressures from two different sensors just
does not make sense.
This changes the logic to be the same for beginning and ending
pressures: we only update it once, with the first relevant sample we
see. But we walk the samples twice: forwards from the beginning to
find the first beginning pressure, and backwards from the end to find
the ending pressure.
That means that as we move on to the second dive computer, we've now
filled in the ending pressure from the first one, and will no longer
update it any more.
NOTE! We don't stop scanning the samples (or the dive computers) just
because we've found a valid pressure value. We'll always walk all the
samples because there might be multiple different cylinders that get
pressure data from different samples (and different dive computers).
We could have some early-out logic when we've filled in all relevant
cylinders, but since this just runs once per dive it's not worth it.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 20:23:55 +00:00
|
|
|
|
|
|
|
|
|
/* Walk the samples from the beginning to find starting pressures.. */
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
for (i = 0; i < dc->samples; i++) {
|
Profile support for multiple concurrent pressure sensors
This finally handles multiple cylinder pressures, both overlapping and
consecutive, and it seems to work on the nasty cases I've thrown at it.
Want to just track five different cylinders all at once, without any
pesky gas switch events? Sure, you can do that. It will show five
different gas pressures for your five cylinders, and they will go down
as you breathe down the cylinders.
I obviously don't have any real data for that case, but I do have a test
file with five actual cylinders that all have samples over the whole
course of the dive. The end result looks messy as hell, but what did
you expect?
HOWEVER.
The only way to do this sanely was
- actually make the "struct plot_info" have all the cylinder pressures
(so no "sensor index and pressure" - every cylinder has a pressure for
every plot info entry)
This obviously makes the plot_info much bigger. We used to have
MAX_CYLINDERS be a fairly generous 8, which seems sane. The planning
code made that 8 be 20. That seems questionable. But whatever.
The good news is that the plot-info should hopefully get freed, and
only be allocated one dive at a time, so the fact that it is big and
nasty shouldn't be a scaling issue, though.
- the "populate_pressure_information()" function had to be rewritten
quite a bit. The good news is that it's actually simpler now, although
I would not go so far as to really call it simple. It's still
complicated and suble, but now it explicitly just does one cylinder at
a time.
It *used* to have this insanely complicated "keep track of the pressure
ranges for every cylinder at once". I just couldn't stand that model
and keep my sanity, so it now just tracks one cylinder at a time, and
doesn't have an array of live data, instead the caller will just call
it for each cylinder.
- get rid of some of our hackier stuff, like the code that populates the
plot_info data code with the currently selected cylinder number, and
clears out any other pressures. That obviously does *not* work when you
may not have a single primary cylinder any more.
Now, the above sounds like all good things. Yeah, it mostly is.
BUT.
There's a few big downsides from the above:
- there's no sane way to do this as a series of small changes.
The change to make the plot_info take an array of cylinder pressures
rather than the sensor+pressure model really isn't amenable to "fix up
one use at a time". When you switch over to the new data structure
model, you have to switch over to the new way of populating the
pressure ranges. The two just go hand in hand.
- Some of our code *depended* on the "sensor+pressure" model. I fixed all
the ones I could sanely fix. There was one particular case that I just
couldn't sanely fix, and I didn't care enough about it to do something
insane.
So the only _known_ breakage is the "TankItem" profile widget. That's
the bar at the bottom of the profile that shows which cylinder is in
use right now. You'd think that would be trivial to fix up, and yes it
would be - I could just use the regular model of
firstcyl = explicit_first_cylinder(dive, dc)
.. then iterate over the gas change events to see the others ..
but the problem with the "TankItem" widget is that it does its own
model, and it has thrown away the dive and the dive computer
information. It just doesn't even know. It only knows what cylinders
there are, and the plot_info. And it just used to look at the sensor
number in the plot_info, and be done with that. That number no longer
exists.
- I have tested it, and I think the code is better, but hey, it's a
fairly large patch to some of the more complex code in our code base.
That "interpolate missing pressure fields" code really isn't pretty. It
may be prettier, but..
Anyway, without further ado, here's the patch. No sign-off yet, because I
do think people should look and comment. But I think the patch is fine,
and I'll fix anythign that anybody can find, *except* for that TankItem
thing that I will refuse to touch. That class is ugly. It needs to have
access to the actual dive.
Note how it actually does remove more lines than it adds, and that's
despite added comments etc. The code really is simpler, but there may be
cases in there that need more work.
Known missing pieces that don't currently take advantage of concurrent
cylinder pressure data:
- the momentary SAC rate coloring for dives will need more work
- dive merging (but we expect to generally normally not merge dive
computers, which is the main source of sensor data)
- actually taking advantage of different sensor data from different
dive computers
But most of all: Testing. Lots and lots of testing to find all the
corner cases.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-27 17:17:05 +00:00
|
|
|
|
int idx;
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
struct sample *sample = dc->sample + i;
|
Fix cylinder end pressure fixup from samples
This bug admittedly hits almost nobody, but if you had multiple cylinder
pressure sensors on the same cylinder (attached to multiple dive
computers, of course), we would take the beginning pressure from the
first dive computer, and the ending pressure from the last dive
computer.
That came about because we'd just walk all the dive computer samples in
order, and the first time we see a relevant sample and we don't have a
beginning pressure, we'd take that pressure. So the beginning pressure
was from the first dive computer, and once we'd seen a valid beginning
pressure, that would never change.
But as we're walking along, we'd continue to update the ending pressure
from the last relevant sample we see, which means that as we go on to
look at the other dive computers, we'd continue to update the ending
pressure with data from them.
And mixing beginning/ending pressures from two different sensors just
does not make sense.
This changes the logic to be the same for beginning and ending
pressures: we only update it once, with the first relevant sample we
see. But we walk the samples twice: forwards from the beginning to
find the first beginning pressure, and backwards from the end to find
the ending pressure.
That means that as we move on to the second dive computer, we've now
filled in the ending pressure from the first one, and will no longer
update it any more.
NOTE! We don't stop scanning the samples (or the dive computers) just
because we've found a valid pressure value. We'll always walk all the
samples because there might be multiple different cylinders that get
pressure data from different samples (and different dive computers).
We could have some early-out logic when we've filled in all relevant
cylinders, but since this just runs once per dive it's not worth it.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 20:23:55 +00:00
|
|
|
|
|
|
|
|
|
if (sample->depth.mm < SURFACE_THRESHOLD)
|
|
|
|
|
continue;
|
|
|
|
|
|
Profile support for multiple concurrent pressure sensors
This finally handles multiple cylinder pressures, both overlapping and
consecutive, and it seems to work on the nasty cases I've thrown at it.
Want to just track five different cylinders all at once, without any
pesky gas switch events? Sure, you can do that. It will show five
different gas pressures for your five cylinders, and they will go down
as you breathe down the cylinders.
I obviously don't have any real data for that case, but I do have a test
file with five actual cylinders that all have samples over the whole
course of the dive. The end result looks messy as hell, but what did
you expect?
HOWEVER.
The only way to do this sanely was
- actually make the "struct plot_info" have all the cylinder pressures
(so no "sensor index and pressure" - every cylinder has a pressure for
every plot info entry)
This obviously makes the plot_info much bigger. We used to have
MAX_CYLINDERS be a fairly generous 8, which seems sane. The planning
code made that 8 be 20. That seems questionable. But whatever.
The good news is that the plot-info should hopefully get freed, and
only be allocated one dive at a time, so the fact that it is big and
nasty shouldn't be a scaling issue, though.
- the "populate_pressure_information()" function had to be rewritten
quite a bit. The good news is that it's actually simpler now, although
I would not go so far as to really call it simple. It's still
complicated and suble, but now it explicitly just does one cylinder at
a time.
It *used* to have this insanely complicated "keep track of the pressure
ranges for every cylinder at once". I just couldn't stand that model
and keep my sanity, so it now just tracks one cylinder at a time, and
doesn't have an array of live data, instead the caller will just call
it for each cylinder.
- get rid of some of our hackier stuff, like the code that populates the
plot_info data code with the currently selected cylinder number, and
clears out any other pressures. That obviously does *not* work when you
may not have a single primary cylinder any more.
Now, the above sounds like all good things. Yeah, it mostly is.
BUT.
There's a few big downsides from the above:
- there's no sane way to do this as a series of small changes.
The change to make the plot_info take an array of cylinder pressures
rather than the sensor+pressure model really isn't amenable to "fix up
one use at a time". When you switch over to the new data structure
model, you have to switch over to the new way of populating the
pressure ranges. The two just go hand in hand.
- Some of our code *depended* on the "sensor+pressure" model. I fixed all
the ones I could sanely fix. There was one particular case that I just
couldn't sanely fix, and I didn't care enough about it to do something
insane.
So the only _known_ breakage is the "TankItem" profile widget. That's
the bar at the bottom of the profile that shows which cylinder is in
use right now. You'd think that would be trivial to fix up, and yes it
would be - I could just use the regular model of
firstcyl = explicit_first_cylinder(dive, dc)
.. then iterate over the gas change events to see the others ..
but the problem with the "TankItem" widget is that it does its own
model, and it has thrown away the dive and the dive computer
information. It just doesn't even know. It only knows what cylinders
there are, and the plot_info. And it just used to look at the sensor
number in the plot_info, and be done with that. That number no longer
exists.
- I have tested it, and I think the code is better, but hey, it's a
fairly large patch to some of the more complex code in our code base.
That "interpolate missing pressure fields" code really isn't pretty. It
may be prettier, but..
Anyway, without further ado, here's the patch. No sign-off yet, because I
do think people should look and comment. But I think the patch is fine,
and I'll fix anythign that anybody can find, *except* for that TankItem
thing that I will refuse to touch. That class is ugly. It needs to have
access to the actual dive.
Note how it actually does remove more lines than it adds, and that's
despite added comments etc. The code really is simpler, but there may be
cases in there that need more work.
Known missing pieces that don't currently take advantage of concurrent
cylinder pressure data:
- the momentary SAC rate coloring for dives will need more work
- dive merging (but we expect to generally normally not merge dive
computers, which is the main source of sensor data)
- actually taking advantage of different sensor data from different
dive computers
But most of all: Testing. Lots and lots of testing to find all the
corner cases.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-27 17:17:05 +00:00
|
|
|
|
for (idx = 0; idx < MAX_SENSORS; idx++)
|
|
|
|
|
fixup_start_pressure(dive, sample->sensor[idx], sample->pressure[idx]);
|
Fix cylinder end pressure fixup from samples
This bug admittedly hits almost nobody, but if you had multiple cylinder
pressure sensors on the same cylinder (attached to multiple dive
computers, of course), we would take the beginning pressure from the
first dive computer, and the ending pressure from the last dive
computer.
That came about because we'd just walk all the dive computer samples in
order, and the first time we see a relevant sample and we don't have a
beginning pressure, we'd take that pressure. So the beginning pressure
was from the first dive computer, and once we'd seen a valid beginning
pressure, that would never change.
But as we're walking along, we'd continue to update the ending pressure
from the last relevant sample we see, which means that as we go on to
look at the other dive computers, we'd continue to update the ending
pressure with data from them.
And mixing beginning/ending pressures from two different sensors just
does not make sense.
This changes the logic to be the same for beginning and ending
pressures: we only update it once, with the first relevant sample we
see. But we walk the samples twice: forwards from the beginning to
find the first beginning pressure, and backwards from the end to find
the ending pressure.
That means that as we move on to the second dive computer, we've now
filled in the ending pressure from the first one, and will no longer
update it any more.
NOTE! We don't stop scanning the samples (or the dive computers) just
because we've found a valid pressure value. We'll always walk all the
samples because there might be multiple different cylinders that get
pressure data from different samples (and different dive computers).
We could have some early-out logic when we've filled in all relevant
cylinders, but since this just runs once per dive it's not worth it.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 20:23:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ..and from the end for ending pressures */
|
|
|
|
|
for (i = dc->samples; --i >= 0; ) {
|
Profile support for multiple concurrent pressure sensors
This finally handles multiple cylinder pressures, both overlapping and
consecutive, and it seems to work on the nasty cases I've thrown at it.
Want to just track five different cylinders all at once, without any
pesky gas switch events? Sure, you can do that. It will show five
different gas pressures for your five cylinders, and they will go down
as you breathe down the cylinders.
I obviously don't have any real data for that case, but I do have a test
file with five actual cylinders that all have samples over the whole
course of the dive. The end result looks messy as hell, but what did
you expect?
HOWEVER.
The only way to do this sanely was
- actually make the "struct plot_info" have all the cylinder pressures
(so no "sensor index and pressure" - every cylinder has a pressure for
every plot info entry)
This obviously makes the plot_info much bigger. We used to have
MAX_CYLINDERS be a fairly generous 8, which seems sane. The planning
code made that 8 be 20. That seems questionable. But whatever.
The good news is that the plot-info should hopefully get freed, and
only be allocated one dive at a time, so the fact that it is big and
nasty shouldn't be a scaling issue, though.
- the "populate_pressure_information()" function had to be rewritten
quite a bit. The good news is that it's actually simpler now, although
I would not go so far as to really call it simple. It's still
complicated and suble, but now it explicitly just does one cylinder at
a time.
It *used* to have this insanely complicated "keep track of the pressure
ranges for every cylinder at once". I just couldn't stand that model
and keep my sanity, so it now just tracks one cylinder at a time, and
doesn't have an array of live data, instead the caller will just call
it for each cylinder.
- get rid of some of our hackier stuff, like the code that populates the
plot_info data code with the currently selected cylinder number, and
clears out any other pressures. That obviously does *not* work when you
may not have a single primary cylinder any more.
Now, the above sounds like all good things. Yeah, it mostly is.
BUT.
There's a few big downsides from the above:
- there's no sane way to do this as a series of small changes.
The change to make the plot_info take an array of cylinder pressures
rather than the sensor+pressure model really isn't amenable to "fix up
one use at a time". When you switch over to the new data structure
model, you have to switch over to the new way of populating the
pressure ranges. The two just go hand in hand.
- Some of our code *depended* on the "sensor+pressure" model. I fixed all
the ones I could sanely fix. There was one particular case that I just
couldn't sanely fix, and I didn't care enough about it to do something
insane.
So the only _known_ breakage is the "TankItem" profile widget. That's
the bar at the bottom of the profile that shows which cylinder is in
use right now. You'd think that would be trivial to fix up, and yes it
would be - I could just use the regular model of
firstcyl = explicit_first_cylinder(dive, dc)
.. then iterate over the gas change events to see the others ..
but the problem with the "TankItem" widget is that it does its own
model, and it has thrown away the dive and the dive computer
information. It just doesn't even know. It only knows what cylinders
there are, and the plot_info. And it just used to look at the sensor
number in the plot_info, and be done with that. That number no longer
exists.
- I have tested it, and I think the code is better, but hey, it's a
fairly large patch to some of the more complex code in our code base.
That "interpolate missing pressure fields" code really isn't pretty. It
may be prettier, but..
Anyway, without further ado, here's the patch. No sign-off yet, because I
do think people should look and comment. But I think the patch is fine,
and I'll fix anythign that anybody can find, *except* for that TankItem
thing that I will refuse to touch. That class is ugly. It needs to have
access to the actual dive.
Note how it actually does remove more lines than it adds, and that's
despite added comments etc. The code really is simpler, but there may be
cases in there that need more work.
Known missing pieces that don't currently take advantage of concurrent
cylinder pressure data:
- the momentary SAC rate coloring for dives will need more work
- dive merging (but we expect to generally normally not merge dive
computers, which is the main source of sensor data)
- actually taking advantage of different sensor data from different
dive computers
But most of all: Testing. Lots and lots of testing to find all the
corner cases.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-27 17:17:05 +00:00
|
|
|
|
int idx;
|
Fix cylinder end pressure fixup from samples
This bug admittedly hits almost nobody, but if you had multiple cylinder
pressure sensors on the same cylinder (attached to multiple dive
computers, of course), we would take the beginning pressure from the
first dive computer, and the ending pressure from the last dive
computer.
That came about because we'd just walk all the dive computer samples in
order, and the first time we see a relevant sample and we don't have a
beginning pressure, we'd take that pressure. So the beginning pressure
was from the first dive computer, and once we'd seen a valid beginning
pressure, that would never change.
But as we're walking along, we'd continue to update the ending pressure
from the last relevant sample we see, which means that as we go on to
look at the other dive computers, we'd continue to update the ending
pressure with data from them.
And mixing beginning/ending pressures from two different sensors just
does not make sense.
This changes the logic to be the same for beginning and ending
pressures: we only update it once, with the first relevant sample we
see. But we walk the samples twice: forwards from the beginning to
find the first beginning pressure, and backwards from the end to find
the ending pressure.
That means that as we move on to the second dive computer, we've now
filled in the ending pressure from the first one, and will no longer
update it any more.
NOTE! We don't stop scanning the samples (or the dive computers) just
because we've found a valid pressure value. We'll always walk all the
samples because there might be multiple different cylinders that get
pressure data from different samples (and different dive computers).
We could have some early-out logic when we've filled in all relevant
cylinders, but since this just runs once per dive it's not worth it.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 20:23:55 +00:00
|
|
|
|
struct sample *sample = dc->sample + i;
|
|
|
|
|
|
|
|
|
|
if (sample->depth.mm < SURFACE_THRESHOLD)
|
|
|
|
|
continue;
|
|
|
|
|
|
Profile support for multiple concurrent pressure sensors
This finally handles multiple cylinder pressures, both overlapping and
consecutive, and it seems to work on the nasty cases I've thrown at it.
Want to just track five different cylinders all at once, without any
pesky gas switch events? Sure, you can do that. It will show five
different gas pressures for your five cylinders, and they will go down
as you breathe down the cylinders.
I obviously don't have any real data for that case, but I do have a test
file with five actual cylinders that all have samples over the whole
course of the dive. The end result looks messy as hell, but what did
you expect?
HOWEVER.
The only way to do this sanely was
- actually make the "struct plot_info" have all the cylinder pressures
(so no "sensor index and pressure" - every cylinder has a pressure for
every plot info entry)
This obviously makes the plot_info much bigger. We used to have
MAX_CYLINDERS be a fairly generous 8, which seems sane. The planning
code made that 8 be 20. That seems questionable. But whatever.
The good news is that the plot-info should hopefully get freed, and
only be allocated one dive at a time, so the fact that it is big and
nasty shouldn't be a scaling issue, though.
- the "populate_pressure_information()" function had to be rewritten
quite a bit. The good news is that it's actually simpler now, although
I would not go so far as to really call it simple. It's still
complicated and suble, but now it explicitly just does one cylinder at
a time.
It *used* to have this insanely complicated "keep track of the pressure
ranges for every cylinder at once". I just couldn't stand that model
and keep my sanity, so it now just tracks one cylinder at a time, and
doesn't have an array of live data, instead the caller will just call
it for each cylinder.
- get rid of some of our hackier stuff, like the code that populates the
plot_info data code with the currently selected cylinder number, and
clears out any other pressures. That obviously does *not* work when you
may not have a single primary cylinder any more.
Now, the above sounds like all good things. Yeah, it mostly is.
BUT.
There's a few big downsides from the above:
- there's no sane way to do this as a series of small changes.
The change to make the plot_info take an array of cylinder pressures
rather than the sensor+pressure model really isn't amenable to "fix up
one use at a time". When you switch over to the new data structure
model, you have to switch over to the new way of populating the
pressure ranges. The two just go hand in hand.
- Some of our code *depended* on the "sensor+pressure" model. I fixed all
the ones I could sanely fix. There was one particular case that I just
couldn't sanely fix, and I didn't care enough about it to do something
insane.
So the only _known_ breakage is the "TankItem" profile widget. That's
the bar at the bottom of the profile that shows which cylinder is in
use right now. You'd think that would be trivial to fix up, and yes it
would be - I could just use the regular model of
firstcyl = explicit_first_cylinder(dive, dc)
.. then iterate over the gas change events to see the others ..
but the problem with the "TankItem" widget is that it does its own
model, and it has thrown away the dive and the dive computer
information. It just doesn't even know. It only knows what cylinders
there are, and the plot_info. And it just used to look at the sensor
number in the plot_info, and be done with that. That number no longer
exists.
- I have tested it, and I think the code is better, but hey, it's a
fairly large patch to some of the more complex code in our code base.
That "interpolate missing pressure fields" code really isn't pretty. It
may be prettier, but..
Anyway, without further ado, here's the patch. No sign-off yet, because I
do think people should look and comment. But I think the patch is fine,
and I'll fix anythign that anybody can find, *except* for that TankItem
thing that I will refuse to touch. That class is ugly. It needs to have
access to the actual dive.
Note how it actually does remove more lines than it adds, and that's
despite added comments etc. The code really is simpler, but there may be
cases in there that need more work.
Known missing pieces that don't currently take advantage of concurrent
cylinder pressure data:
- the momentary SAC rate coloring for dives will need more work
- dive merging (but we expect to generally normally not merge dive
computers, which is the main source of sensor data)
- actually taking advantage of different sensor data from different
dive computers
But most of all: Testing. Lots and lots of testing to find all the
corner cases.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-27 17:17:05 +00:00
|
|
|
|
for (idx = 0; idx < MAX_SENSORS; idx++)
|
|
|
|
|
fixup_end_pressure(dive, sample->sensor[idx], sample->pressure[idx]);
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-04-05 14:40:32 +00:00
|
|
|
|
simplify_dc_pressures(dc);
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-04-02 20:06:54 +00:00
|
|
|
|
int find_best_gasmix_match(struct gasmix *mix, cylinder_t array[], unsigned int used)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
int best = -1, score = INT_MAX;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
const cylinder_t *match;
|
|
|
|
|
int distance;
|
|
|
|
|
|
|
|
|
|
if (used & (1 << i))
|
|
|
|
|
continue;
|
|
|
|
|
match = array + i;
|
|
|
|
|
if (cylinder_nodata(match))
|
|
|
|
|
continue;
|
|
|
|
|
distance = gasmix_distance(mix, &match->gasmix);
|
|
|
|
|
if (distance >= score)
|
|
|
|
|
continue;
|
|
|
|
|
best = i;
|
|
|
|
|
score = distance;
|
|
|
|
|
}
|
|
|
|
|
return best;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Match a gas change event against the cylinders we have
|
|
|
|
|
*/
|
2016-04-05 14:40:32 +00:00
|
|
|
|
static bool validate_gaschange(struct dive *dive, struct event *event)
|
2016-04-02 20:06:54 +00:00
|
|
|
|
{
|
|
|
|
|
int index;
|
|
|
|
|
int o2, he, value;
|
|
|
|
|
|
|
|
|
|
/* We'll get rid of the per-event gasmix, but for now sanitize it */
|
|
|
|
|
if (gasmix_is_air(&event->gas.mix))
|
|
|
|
|
event->gas.mix.o2.permille = 0;
|
|
|
|
|
|
|
|
|
|
/* Do we already have a cylinder index for this gasmix? */
|
|
|
|
|
if (event->gas.index >= 0)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
index = find_best_gasmix_match(&event->gas.mix, dive->cylinder, 0);
|
|
|
|
|
if (index < 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
/* Fix up the event to have the right information */
|
|
|
|
|
event->gas.index = index;
|
|
|
|
|
event->gas.mix = dive->cylinder[index].gasmix;
|
|
|
|
|
|
|
|
|
|
/* Convert to odd libdivecomputer format */
|
|
|
|
|
o2 = get_o2(&event->gas.mix);
|
|
|
|
|
he = get_he(&event->gas.mix);
|
|
|
|
|
|
|
|
|
|
o2 = (o2 + 5) / 10;
|
|
|
|
|
he = (he + 5) / 10;
|
|
|
|
|
value = o2 + (he << 16);
|
|
|
|
|
|
|
|
|
|
event->value = value;
|
|
|
|
|
if (he)
|
|
|
|
|
event->type = SAMPLE_EVENT_GASCHANGE2;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Clean up event, return true if event is ok, false if it should be dropped as bogus */
|
2016-04-05 14:40:32 +00:00
|
|
|
|
static bool validate_event(struct dive *dive, struct event *event)
|
2016-04-02 20:06:54 +00:00
|
|
|
|
{
|
|
|
|
|
if (event_is_gaschange(event))
|
2016-04-05 14:40:32 +00:00
|
|
|
|
return validate_gaschange(dive, event);
|
2016-04-02 20:06:54 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void fixup_dc_gasswitch(struct dive *dive, struct divecomputer *dc)
|
|
|
|
|
{
|
|
|
|
|
struct event **evp, *event;
|
|
|
|
|
|
|
|
|
|
evp = &dc->events;
|
|
|
|
|
while ((event = *evp) != NULL) {
|
2016-04-05 14:40:32 +00:00
|
|
|
|
if (validate_event(dive, event)) {
|
2016-04-02 20:06:54 +00:00
|
|
|
|
evp = &event->next;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Delete this event and try the next one */
|
|
|
|
|
*evp = event->next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
static void fixup_dive_dc(struct dive *dive, struct divecomputer *dc)
|
|
|
|
|
{
|
|
|
|
|
/* Add device information to table */
|
|
|
|
|
if (dc->deviceid && (dc->serial || dc->fw_version))
|
|
|
|
|
create_device_node(dc->model, dc->deviceid, dc->serial, dc->fw_version, "");
|
|
|
|
|
|
|
|
|
|
/* Fixup duration and mean depth */
|
|
|
|
|
fixup_dc_duration(dc);
|
|
|
|
|
|
|
|
|
|
/* Fix up sample depth data */
|
|
|
|
|
fixup_dc_depths(dive, dc);
|
|
|
|
|
|
|
|
|
|
/* Fix up dive temperatures based on dive computer samples */
|
|
|
|
|
fixup_dc_temp(dive, dc);
|
|
|
|
|
|
2016-04-02 20:06:54 +00:00
|
|
|
|
/* Fix up gas switch events */
|
|
|
|
|
fixup_dc_gasswitch(dive, dc);
|
|
|
|
|
|
Split up fixup_dive_dc() into multiple smaller independent functions
fixup_dive_dc() is called for each dive computer when we add a new dive.
It does various housekeeping functions, cleaning up the sample data, and
fixing up dive details as a result of the sample data.
The function has grown to be a monster over time, and particularly the
central "walk every sample" loop has become an unreadable mess.
And the thing is, this isn't even all that performance-critical: it's
only done once per dive and dc, and there is no reason to have a single
illegible and complex loop.
So split up that loop into several smaller pieces that each will loop
individually over the sample data, and do just one thing. So now we
have separate functions for
- fixing up the depth samples with interpolation
- fixing up dive temperature data
- correcting the cylinder pressure sensor index
- cleaning up the actual sample pressures
Yes, this way we walk the samples multiple times, but the end result is
that the code is much easier to understand. There should be no actual
behavioral differences from this cleanup, except for the fact that since
the code is much more understandable, this cleanup also fixed a bug:
In the temperature fixup, we would fix up the overall dive temperatures
based on the dive computer temperatures. But we would then fix up the
overall dive computer temperature based on the sample temperature
*afterwards*, which wouldn't then be reflected in the overall dive
temperatures.
There was another non-symptomatic bug that became obvious when doing
this cleanup: the code used to calculate a 'depthtime' over the dive
that was never actually used. That's a historical artifact of old code
that had become dead when the average depth calculations were moved to a
function of their own earlier.
This is preparatory for fixing the overall cylinder pressure stats,
which are currently wrong for dives with multiple dive computers: we
currently take the starting cylinder pressure from the *first* dive
computer that has cylinder pressure information, but we take the ending
cylinder pressure from the *last* dive computer with cylinder pressure
information.
This does not fix that bug, but without this cleanup fixing that would
be a nightmare due to the previous complicated "do everything in one
single loop" model.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-01 19:32:56 +00:00
|
|
|
|
/* Fix up cylinder pressures based on DC info */
|
|
|
|
|
fixup_dive_pressures(dive, dc);
|
2011-09-04 20:06:47 +00:00
|
|
|
|
|
2013-02-09 00:35:39 +00:00
|
|
|
|
fixup_dc_events(dc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct dive *fixup_dive(struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
struct divecomputer *dc;
|
|
|
|
|
|
|
|
|
|
sanitize_cylinder_info(dive);
|
|
|
|
|
dive->maxcns = dive->cns;
|
|
|
|
|
|
2015-10-07 16:12:41 +00:00
|
|
|
|
/*
|
|
|
|
|
* Use the dive's temperatures for minimum and maximum in case
|
|
|
|
|
* we do not have temperatures recorded by DC.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
update_min_max_temperatures(dive, dive->watertemp);
|
|
|
|
|
|
2014-10-11 11:25:52 +00:00
|
|
|
|
for_each_dc (dive, dc)
|
2014-03-03 21:25:55 +00:00
|
|
|
|
fixup_dive_dc(dive, dc);
|
2013-02-17 20:05:08 +00:00
|
|
|
|
|
2013-02-09 00:35:39 +00:00
|
|
|
|
fixup_water_salinity(dive);
|
|
|
|
|
fixup_surface_pressure(dive);
|
2013-02-09 14:50:53 +00:00
|
|
|
|
fixup_meandepth(dive);
|
2013-02-09 15:12:30 +00:00
|
|
|
|
fixup_duration(dive);
|
2013-02-09 15:41:15 +00:00
|
|
|
|
fixup_watertemp(dive);
|
|
|
|
|
fixup_airtemp(dive);
|
2011-10-22 15:12:30 +00:00
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
2011-11-09 15:37:25 +00:00
|
|
|
|
cylinder_t *cyl = dive->cylinder + i;
|
|
|
|
|
add_cylinder_description(&cyl->type);
|
2011-11-10 23:33:38 +00:00
|
|
|
|
if (same_rounded_pressure(cyl->sample_start, cyl->start))
|
2011-11-09 15:37:25 +00:00
|
|
|
|
cyl->start.mbar = 0;
|
2011-11-10 23:33:38 +00:00
|
|
|
|
if (same_rounded_pressure(cyl->sample_end, cyl->end))
|
2011-11-09 15:37:25 +00:00
|
|
|
|
cyl->end.mbar = 0;
|
2011-10-22 15:12:30 +00:00
|
|
|
|
}
|
2015-06-18 00:58:31 +00:00
|
|
|
|
update_cylinder_related_info(dive);
|
2012-08-06 21:03:24 +00:00
|
|
|
|
for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) {
|
|
|
|
|
weightsystem_t *ws = dive->weightsystem + i;
|
|
|
|
|
add_weightsystem_description(ws);
|
|
|
|
|
}
|
2014-05-18 21:53:28 +00:00
|
|
|
|
/* we should always have a uniq ID as that gets assigned during alloc_dive(),
|
|
|
|
|
* but we want to make sure... */
|
|
|
|
|
if (!dive->id)
|
|
|
|
|
dive->id = dive_getUniqID(dive);
|
2011-10-22 15:12:30 +00:00
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
|
return dive;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Don't pick a zero for MERGE_MIN() */
|
|
|
|
|
#define MERGE_MAX(res, a, b, n) res->n = MAX(a->n, b->n)
|
2014-02-28 04:09:57 +00:00
|
|
|
|
#define MERGE_MIN(res, a, b, n) res->n = (a->n) ? (b->n) ? MIN(a->n, b->n) : (a->n) : (b->n)
|
2011-09-23 01:01:01 +00:00
|
|
|
|
#define MERGE_TXT(res, a, b, n) res->n = merge_text(a->n, b->n)
|
2011-09-23 03:50:07 +00:00
|
|
|
|
#define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
Import Datatrak/WLog files
Sequentially parses a file, expected to be a Datatrak/WLog divelog, and
converts the dive info into Subsurface's dive structure.
As my first DC, back in 90s, was an Aladin Air X, the obvious choice of log
software was DTrak (Win version). After using it for some time we moved to WLog
(shareware software more user friendly than Dtrak, printing capable, and still
better, it runs under wine, which, as linux user, was definitive for me). Then,
some years later, my last Aladin died and I moved to an OSTC, forcing me to
look for a software that support this DC.
I found JDivelog which was capable of import Dtrak logs and used it for some
time until discovered Subsurface existence and devoted to it.
The fact was that importing Dtrak dives in JDivelog and then re-importing them
in Subsurface caused a significant data loss (mainly in the profile events and
alarms) and weird location of some other info in the dive notes (mostly tag
items in the original Dtrak software). This situation can't actually be solved
with tools like divelogs.de which causes similar if no greater data loss.
Although this won't be a core feature for Subsurface, I expect it can be useful
for some other divers as has been for me.
Comments and issues:
Datatrak/Wlog files include a lot of diving data which are not directly
supported in Subsurface, in these cases we choose mostly to use "tags".
The lack of some important info in Datatrak archives (e.g. tank's initial
pressure) forces us to do some arbitrary assumptions (e.g. initial pressure =
200 bar).
There might be archives coming directly from old DOS days, as first versions
of Datatrak run on that OS; they were coded CP437 or CP850, while dive logs
coming from Win versions seems to be coded CP1252. Finally, Wlog seems to use a
mixed confusing style. Program directly converts some of the old encoded chars
to iso8859 but is expected there be some issues with non alphabetic chars, e.g.
"ª".
There are two text fields: "Other activities" and "Dive notes", both limited to
256 char size. We have merged them in Subsurface's "Dive Notes" although the
first one could be "tagged", but we're unsure that the user had filled it in
a tag friendly way.
WLog adds some information to the dive and lets the user to write more than
256 chars notes. This is achieved, while keeping compatibility with DTrak
divelogs, by adding a complementary file named equally as the .log file and
with .add extension where all this info is stored. We have, still, not worked
with this complementary files.
This work is based on the paper referenced in butracker #194 which has some
errors (e.g. beginning of log and beginning of dive are changed) and a lot of
bytes of unknown meaning. Example.log shows, at least, one more byte than those
referred in the paper for the O2 Aladin computer, this could be a byte referred
to the use of SCR but the lack of an OC dive with O2 computer makes impossible
for us to compare.
The only way we have figured out to distinguish a priori between SCR and non
SCR dives with O2 computers is that the dives are tagged with a "rebreather"
tag. Obviously this is not a very trusty way of doing things. In SCR dives,
the O2% in mix means, probably, the maximum O2% in the circuit, not the O2%
of the EAN mix in the tanks, which would be unknown in this case.
The list of DCs related in bug #194 paper seems incomplete, we have added
one or two from WLog and discarded those which are known to exist but whose
model is unknown, grouping them under the imaginative name of "unknown". The
list can easily be increased in the future if we ever know the models
identifiers.
BTW, in Example.log, 0x00 identifier is used for some DC dives and from my own
divelogs is inferred that 0x00 is used for manually entered dives, this could
easily be an error in Example.log coming from a preproduction DC model.
Example.log which is shipped in datatrak package is included in dives
directory for testing pourposes.
[Dirk Hohndel: some small cleanups, merged with latest master, support
divesites, remove the pointless memset() before free() calls
add to cmake build]
Signed-off-by: Salvador Cuñat <salvador.cunat@gmail.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-11-05 18:38:27 +00:00
|
|
|
|
struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc)
|
2011-09-03 20:19:26 +00:00
|
|
|
|
{
|
2012-11-24 02:51:27 +00:00
|
|
|
|
struct sample *p = prepare_sample(dc);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
2012-11-24 02:05:38 +00:00
|
|
|
|
if (p) {
|
|
|
|
|
*p = *sample;
|
|
|
|
|
p->time.seconds = time;
|
2012-11-24 02:51:27 +00:00
|
|
|
|
finish_sample(dc);
|
2012-11-24 02:05:38 +00:00
|
|
|
|
}
|
|
|
|
|
return p;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-17 21:20:10 +00:00
|
|
|
|
/*
|
|
|
|
|
* This is like add_sample(), but if the distance from the last sample
|
|
|
|
|
* is excessive, we add two surface samples in between.
|
|
|
|
|
*
|
|
|
|
|
* This is so that if you merge two non-overlapping dives, we make sure
|
|
|
|
|
* that the time in between the dives is at the surface, not some "last
|
|
|
|
|
* sample that happened to be at a depth of 1.2m".
|
|
|
|
|
*/
|
2012-11-24 02:51:27 +00:00
|
|
|
|
static void merge_one_sample(struct sample *sample, int time, struct divecomputer *dc)
|
2012-11-17 21:20:10 +00:00
|
|
|
|
{
|
2014-02-28 04:09:57 +00:00
|
|
|
|
int last = dc->samples - 1;
|
2012-11-17 21:20:10 +00:00
|
|
|
|
if (last >= 0) {
|
|
|
|
|
static struct sample surface;
|
2012-12-07 17:42:47 +00:00
|
|
|
|
struct sample *prev = dc->sample + last;
|
|
|
|
|
int last_time = prev->time.seconds;
|
|
|
|
|
int last_depth = prev->depth.mm;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Only do surface events if the samples are more than
|
|
|
|
|
* a minute apart, and shallower than 5m
|
|
|
|
|
*/
|
|
|
|
|
if (time > last_time + 60 && last_depth < 5000) {
|
2014-02-28 04:09:57 +00:00
|
|
|
|
add_sample(&surface, last_time + 20, dc);
|
2012-11-24 02:51:27 +00:00
|
|
|
|
add_sample(&surface, time - 20, dc);
|
2012-11-17 21:20:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2012-11-24 02:51:27 +00:00
|
|
|
|
add_sample(sample, time, dc);
|
2012-11-17 21:20:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
|
/*
|
|
|
|
|
* Merge samples. Dive 'a' is "offset" seconds before Dive 'b'
|
|
|
|
|
*/
|
2012-11-24 02:51:27 +00:00
|
|
|
|
static void merge_samples(struct divecomputer *res, struct divecomputer *a, struct divecomputer *b, int offset)
|
2011-09-03 20:19:26 +00:00
|
|
|
|
{
|
|
|
|
|
int asamples = a->samples;
|
|
|
|
|
int bsamples = b->samples;
|
|
|
|
|
struct sample *as = a->sample;
|
|
|
|
|
struct sample *bs = b->sample;
|
|
|
|
|
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
/*
|
|
|
|
|
* We want a positive sample offset, so that sample
|
|
|
|
|
* times are always positive. So if the samples for
|
|
|
|
|
* 'b' are before the samples for 'a' (so the offset
|
|
|
|
|
* is negative), we switch a and b around, and use
|
|
|
|
|
* the reverse offset.
|
|
|
|
|
*/
|
|
|
|
|
if (offset < 0) {
|
|
|
|
|
offset = -offset;
|
|
|
|
|
asamples = bsamples;
|
|
|
|
|
bsamples = a->samples;
|
|
|
|
|
as = bs;
|
|
|
|
|
bs = a->sample;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
|
for (;;) {
|
|
|
|
|
int at, bt;
|
|
|
|
|
struct sample sample;
|
|
|
|
|
|
|
|
|
|
if (!res)
|
2012-11-24 02:51:27 +00:00
|
|
|
|
return;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
|
|
at = asamples ? as->time.seconds : -1;
|
|
|
|
|
bt = bsamples ? bs->time.seconds + offset : -1;
|
|
|
|
|
|
|
|
|
|
/* No samples? All done! */
|
|
|
|
|
if (at < 0 && bt < 0)
|
2012-11-24 02:51:27 +00:00
|
|
|
|
return;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
|
|
/* Only samples from a? */
|
|
|
|
|
if (bt < 0) {
|
2014-02-28 04:09:57 +00:00
|
|
|
|
add_sample_a:
|
2012-11-24 02:05:38 +00:00
|
|
|
|
merge_one_sample(as, at, res);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
as++;
|
|
|
|
|
asamples--;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Only samples from b? */
|
|
|
|
|
if (at < 0) {
|
2014-02-28 04:09:57 +00:00
|
|
|
|
add_sample_b:
|
2012-11-24 02:05:38 +00:00
|
|
|
|
merge_one_sample(bs, bt, res);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
bs++;
|
|
|
|
|
bsamples--;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (at < bt)
|
|
|
|
|
goto add_sample_a;
|
|
|
|
|
if (at > bt)
|
|
|
|
|
goto add_sample_b;
|
|
|
|
|
|
|
|
|
|
/* same-time sample: add a merged sample. Take the non-zero ones */
|
|
|
|
|
sample = *bs;
|
|
|
|
|
if (as->depth.mm)
|
|
|
|
|
sample.depth = as->depth;
|
|
|
|
|
if (as->temperature.mkelvin)
|
|
|
|
|
sample.temperature = as->temperature;
|
2017-07-20 21:39:02 +00:00
|
|
|
|
if (as->pressure[0].mbar)
|
|
|
|
|
sample.pressure[0] = as->pressure[0];
|
Start cleaning up sensor indexing for multiple sensors
This is a very timid start at making us actually use multiple sensors
without the magical special case for just CCR oxygen tracking.
It mainly does:
- turn the "sample->sensor" index into an array of two indexes, to
match the pressures themselves.
- get rid of dive->{oxygen_cylinder_index,diluent_cylinder_index},
since a CCR dive should now simply set the sample->sensor[] indices
correctly instead.
- in a couple of places, start actually looping over the sensors rather
than special-case the O2 case (although often the small "loops" are
just unrolled, since it's just two cases.
but in many cases we still end up only covering the zero sensor case,
because the CCR O2 sensor code coverage was fairly limited.
It's entirely possible (even likely) that this migth break some existing
case: it tries to be a fairly direct ("stupid") translation of the old
code, but unlike the preparatory patch this does actually does change
some semantics.
For example, right now the git loader code assumes that if the git save
data contains a o2pressure entry, it just hardcodes the O2 sensor index
to 1.
In fact, one issue is going to simply be that our file formats do not
have that multiple sensor format, but instead had very clearly encoded
things as being the CCR O2 pressure sensor.
But this is hopefully close to usable, and I will need feedback (and
maybe test cases) from people who have existing CCR dives with pressure
data.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-21 02:49:45 +00:00
|
|
|
|
if (as->sensor[0])
|
|
|
|
|
sample.sensor[0] = as->sensor[0];
|
|
|
|
|
if (as->pressure[1].mbar)
|
|
|
|
|
sample.pressure[1] = as->pressure[1];
|
|
|
|
|
if (as->sensor[1])
|
|
|
|
|
sample.sensor[1] = as->sensor[1];
|
2012-12-08 04:08:29 +00:00
|
|
|
|
if (as->cns)
|
|
|
|
|
sample.cns = as->cns;
|
2014-10-19 14:07:07 +00:00
|
|
|
|
if (as->setpoint.mbar)
|
|
|
|
|
sample.setpoint = as->setpoint;
|
2012-12-11 20:30:34 +00:00
|
|
|
|
if (as->ndl.seconds)
|
|
|
|
|
sample.ndl = as->ndl;
|
|
|
|
|
if (as->stoptime.seconds)
|
|
|
|
|
sample.stoptime = as->stoptime;
|
|
|
|
|
if (as->stopdepth.mm)
|
|
|
|
|
sample.stopdepth = as->stopdepth;
|
2012-12-31 02:11:01 +00:00
|
|
|
|
if (as->in_deco)
|
2014-01-15 18:54:41 +00:00
|
|
|
|
sample.in_deco = true;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
2012-11-24 02:05:38 +00:00
|
|
|
|
merge_one_sample(&sample, at, res);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
|
|
as++;
|
|
|
|
|
bs++;
|
|
|
|
|
asamples--;
|
|
|
|
|
bsamples--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *merge_text(const char *a, const char *b)
|
|
|
|
|
{
|
|
|
|
|
char *res;
|
Small changes in the memory management when dive-merging
This patch makes a couple of modifications:
1) divelist.c:delete_single_dive() now tries to free all memory associated
with a dive, such as the string values for divemaster, location, notes &
etc.
2) dive.c:merge_text(), now always makes a copy in memory for the returned
string - either combined or one of the two which are passed
to the function.
The reason for the above two changes is that when (say) importing the same
data over and over, technically a merge will occur for the contained dives,
but mapped pointers can go out of scope.
main.c:report_dives() calls try_to_merge() and if succeeds the two dives
that were merged are deleted from the table. when we delete a dive,
we now make sure all string data is cleared with it, but also in the actual merge
itself, which precedes, copies of the merged texts are made (with merge_text()),
so that the new, resulted dive has his own text allocations.
Signed-off-by: Lubomir I. Ivanov <neolit123@gmail.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-24 01:53:25 +00:00
|
|
|
|
if (!a && !b)
|
|
|
|
|
return NULL;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
if (!a || !*a)
|
2014-07-03 04:05:22 +00:00
|
|
|
|
return copy_string(b);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
if (!b || !*b)
|
Small changes in the memory management when dive-merging
This patch makes a couple of modifications:
1) divelist.c:delete_single_dive() now tries to free all memory associated
with a dive, such as the string values for divemaster, location, notes &
etc.
2) dive.c:merge_text(), now always makes a copy in memory for the returned
string - either combined or one of the two which are passed
to the function.
The reason for the above two changes is that when (say) importing the same
data over and over, technically a merge will occur for the contained dives,
but mapped pointers can go out of scope.
main.c:report_dives() calls try_to_merge() and if succeeds the two dives
that were merged are deleted from the table. when we delete a dive,
we now make sure all string data is cleared with it, but also in the actual merge
itself, which precedes, copies of the merged texts are made (with merge_text()),
so that the new, resulted dive has his own text allocations.
Signed-off-by: Lubomir I. Ivanov <neolit123@gmail.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-24 01:53:25 +00:00
|
|
|
|
return strdup(a);
|
2014-02-28 04:09:57 +00:00
|
|
|
|
if (!strcmp(a, b))
|
2014-07-03 04:05:22 +00:00
|
|
|
|
return copy_string(a);
|
2012-12-28 13:43:04 +00:00
|
|
|
|
res = malloc(strlen(a) + strlen(b) + 32);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
if (!res)
|
|
|
|
|
return (char *)a;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
sprintf(res, translate("gettextFromC", "(%s) or (%s)"), a, b);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
#define SORT(a, b, field) \
|
|
|
|
|
if (a->field != b->field) \
|
2014-03-05 20:19:45 +00:00
|
|
|
|
return a->field < b->field ? -1 : 1
|
2011-09-23 03:28:04 +00:00
|
|
|
|
|
|
|
|
|
static int sort_event(struct event *a, struct event *b)
|
|
|
|
|
{
|
2014-02-28 04:09:57 +00:00
|
|
|
|
SORT(a, b, time.seconds);
|
|
|
|
|
SORT(a, b, type);
|
|
|
|
|
SORT(a, b, flags);
|
|
|
|
|
SORT(a, b, value);
|
2011-09-23 03:28:04 +00:00
|
|
|
|
return strcmp(a->name, b->name);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-04 04:14:51 +00:00
|
|
|
|
static int same_gas(struct event *a, struct event *b)
|
|
|
|
|
{
|
|
|
|
|
if (a->type == b->type && a->flags == b->flags && a->value == b->value && !strcmp(a->name, b->name) &&
|
2017-10-08 03:14:57 +00:00
|
|
|
|
same_gasmix(&a->gas.mix, &b->gas.mix)) {
|
2016-10-04 04:14:51 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
|
static void merge_events(struct divecomputer *res, struct divecomputer *src1, struct divecomputer *src2, int offset)
|
2011-09-23 03:28:04 +00:00
|
|
|
|
{
|
|
|
|
|
struct event *a, *b;
|
|
|
|
|
struct event **p = &res->events;
|
2017-04-29 23:21:41 +00:00
|
|
|
|
struct event *last_gas = NULL;
|
2011-09-23 03:28:04 +00:00
|
|
|
|
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
/* Always use positive offsets */
|
|
|
|
|
if (offset < 0) {
|
2012-11-24 02:51:27 +00:00
|
|
|
|
struct divecomputer *tmp;
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
|
|
|
|
|
offset = -offset;
|
|
|
|
|
tmp = src1;
|
|
|
|
|
src1 = src2;
|
|
|
|
|
src2 = tmp;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-23 03:28:04 +00:00
|
|
|
|
a = src1->events;
|
|
|
|
|
b = src2->events;
|
|
|
|
|
while (b) {
|
|
|
|
|
b->time.seconds += offset;
|
|
|
|
|
b = b->next;
|
|
|
|
|
}
|
|
|
|
|
b = src2->events;
|
|
|
|
|
|
|
|
|
|
while (a || b) {
|
|
|
|
|
int s;
|
2017-04-29 23:21:41 +00:00
|
|
|
|
struct event *pick;
|
|
|
|
|
|
2011-09-23 03:28:04 +00:00
|
|
|
|
if (!b) {
|
|
|
|
|
*p = a;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (!a) {
|
|
|
|
|
*p = b;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
s = sort_event(a, b);
|
2016-10-04 04:14:51 +00:00
|
|
|
|
|
2017-04-29 23:21:41 +00:00
|
|
|
|
/* Identical events? Just skip one of them (we pick a) */
|
|
|
|
|
if (!s) {
|
|
|
|
|
a = a->next;
|
2016-10-04 04:14:51 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-29 23:21:41 +00:00
|
|
|
|
/* Otherwise, pick the one that sorts first */
|
|
|
|
|
if (s < 0) {
|
|
|
|
|
pick = a;
|
|
|
|
|
a = a->next;
|
|
|
|
|
} else {
|
|
|
|
|
pick = b;
|
2011-09-23 03:28:04 +00:00
|
|
|
|
b = b->next;
|
|
|
|
|
}
|
2017-04-29 23:21:41 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If that's a gas-change that matches the previous
|
|
|
|
|
* gas change, we'll just skip it
|
|
|
|
|
*/
|
|
|
|
|
if (event_is_gaschange(pick)) {
|
|
|
|
|
if (last_gas && same_gas(pick, last_gas))
|
|
|
|
|
continue;
|
|
|
|
|
last_gas = pick;
|
2011-09-23 03:28:04 +00:00
|
|
|
|
}
|
2017-04-29 23:21:41 +00:00
|
|
|
|
|
|
|
|
|
/* Add it to the target list */
|
|
|
|
|
*p = pick;
|
|
|
|
|
p = &pick->next;
|
2011-09-23 03:28:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-29 18:27:14 +00:00
|
|
|
|
static void merge_weightsystem_info(weightsystem_t *res, weightsystem_t *a, weightsystem_t *b)
|
|
|
|
|
{
|
|
|
|
|
if (!a->weight.grams)
|
|
|
|
|
a = b;
|
|
|
|
|
*res = *a;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-17 00:09:59 +00:00
|
|
|
|
/* get_cylinder_idx_by_use(): Find the index of the first cylinder with a particular CCR use type.
|
Calculate nitrogen and helium gas pressures for CCR after import from CSV
Currently the gas pressures stored in structures of pressure are
calculated using the gasmix composition of the currently selected
cylinder. But with CCR dives the default cylinder is the oxygen
cylinder (here, index 0). However, the gas pressures need to
be calculated using gasmix data from cylinder 1 (the diluent
cylinder). This patch allows setting the appropriate cylinder
for calculating the values in the structures of pressure. It
also allows for correctly calculating gas pressures for any
open circuit cylinders (e.g. bailout) that a CCR diver may
use. This is performed as follows:
1) In dive.h create an enum variable {oxygen, diluent, bailout}
2) Within the definition of cylinder_t, add a member: cylinder_use_type
This stores an enum variable, one of the above.
3) In file.c where the Poseidon CSV data are read in, assign
the appropriate enum values to each of the cylinders.
4) Within the definition of structure dive, add two members:
int oxygen_cylinder_index
int diluent_cylinder_index
This will keep the indices of the two main CCR cylinders.
5) In dive.c create a function get_cylinder_use(). This scans the
cylinders for that dive, looking for a cylinder that has a
particular cylinder_use_type and returns that cylinder index.
6) In dive.c create a function fixup_cylinder_use() that stores the
indices of the oxygen and diluent cylinders in the variables
dive->oxygen_cylinder_index and dive->diluent_cylinder_index,
making use of the function in 4) above.
7) In profile.c, modify function calculate_gas_information_new()
to use the above functions for CCR dives to find the oxygen and
diluent cylinders and to calculate partail gas pressures based
on the diluent cylinder gas mix.
This results in the correct calculation of gas partial pressures
in the case of CCR dives, displaying the correct partial pressure
graphs in the dive profile widget.
Signed-off-by: willem ferguson <willemferguson@zoology.up.ac.za>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-11-03 20:11:00 +00:00
|
|
|
|
* The index returned corresponds to that of the first cylinder with a cylinder_use that
|
|
|
|
|
* equals the appropriate enum value [oxygen, diluent, bailout] given by cylinder_use_type.
|
|
|
|
|
* A negative number returned indicates that a match could not be found.
|
|
|
|
|
* Call parameters: dive = the dive being processed
|
|
|
|
|
* cylinder_use_type = an enum, one of {oxygen, diluent, bailout} */
|
2014-11-17 00:09:59 +00:00
|
|
|
|
extern int get_cylinder_idx_by_use(struct dive *dive, enum cylinderuse cylinder_use_type)
|
Calculate nitrogen and helium gas pressures for CCR after import from CSV
Currently the gas pressures stored in structures of pressure are
calculated using the gasmix composition of the currently selected
cylinder. But with CCR dives the default cylinder is the oxygen
cylinder (here, index 0). However, the gas pressures need to
be calculated using gasmix data from cylinder 1 (the diluent
cylinder). This patch allows setting the appropriate cylinder
for calculating the values in the structures of pressure. It
also allows for correctly calculating gas pressures for any
open circuit cylinders (e.g. bailout) that a CCR diver may
use. This is performed as follows:
1) In dive.h create an enum variable {oxygen, diluent, bailout}
2) Within the definition of cylinder_t, add a member: cylinder_use_type
This stores an enum variable, one of the above.
3) In file.c where the Poseidon CSV data are read in, assign
the appropriate enum values to each of the cylinders.
4) Within the definition of structure dive, add two members:
int oxygen_cylinder_index
int diluent_cylinder_index
This will keep the indices of the two main CCR cylinders.
5) In dive.c create a function get_cylinder_use(). This scans the
cylinders for that dive, looking for a cylinder that has a
particular cylinder_use_type and returns that cylinder index.
6) In dive.c create a function fixup_cylinder_use() that stores the
indices of the oxygen and diluent cylinders in the variables
dive->oxygen_cylinder_index and dive->diluent_cylinder_index,
making use of the function in 4) above.
7) In profile.c, modify function calculate_gas_information_new()
to use the above functions for CCR dives to find the oxygen and
diluent cylinders and to calculate partail gas pressures based
on the diluent cylinder gas mix.
This results in the correct calculation of gas partial pressures
in the case of CCR dives, displaying the correct partial pressure
graphs in the dive profile widget.
Signed-off-by: willem ferguson <willemferguson@zoology.up.ac.za>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-11-03 20:11:00 +00:00
|
|
|
|
{
|
|
|
|
|
int cylinder_index;
|
|
|
|
|
for (cylinder_index = 0; cylinder_index < MAX_CYLINDERS; cylinder_index++) {
|
|
|
|
|
if (dive->cylinder[cylinder_index].cylinder_use == cylinder_use_type)
|
|
|
|
|
return cylinder_index; // return the index of the cylinder with that cylinder use type
|
|
|
|
|
}
|
|
|
|
|
return -1; // negative number means cylinder_use_type not found in list of cylinders
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-25 16:01:16 +00:00
|
|
|
|
int gasmix_distance(const struct gasmix *a, const struct gasmix *b)
|
2013-03-28 02:04:46 +00:00
|
|
|
|
{
|
2013-03-28 17:06:43 +00:00
|
|
|
|
int a_o2 = get_o2(a), b_o2 = get_o2(b);
|
|
|
|
|
int a_he = get_he(a), b_he = get_he(b);
|
2013-03-28 02:04:46 +00:00
|
|
|
|
int delta_o2 = a_o2 - b_o2, delta_he = a_he - b_he;
|
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
delta_he = delta_he * delta_he;
|
|
|
|
|
delta_o2 = delta_o2 * delta_o2;
|
2013-03-28 02:04:46 +00:00
|
|
|
|
return delta_he + delta_o2;
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-12 18:46:41 +00:00
|
|
|
|
/* fill_pressures(): Compute partial gas pressures in bar from gasmix and ambient pressures, possibly for OC or CCR, to be
|
2015-11-16 09:42:36 +00:00
|
|
|
|
* extended to PSCT. This function does the calculations of gas pressures applicable to a single point on the dive profile.
|
2014-10-13 19:19:21 +00:00
|
|
|
|
* The structure "pressures" is used to return calculated gas pressures to the calling software.
|
2014-10-12 18:46:41 +00:00
|
|
|
|
* Call parameters: po2 = po2 value applicable to the record in calling function
|
|
|
|
|
* amb_pressure = ambient pressure applicable to the record in calling function
|
|
|
|
|
* *pressures = structure for communicating o2 sensor values from and gas pressures to the calling function.
|
|
|
|
|
* *mix = structure containing cylinder gas mixture information.
|
|
|
|
|
* This function called by: calculate_gas_information_new() in profile.c; add_segment() in deco.c.
|
|
|
|
|
*/
|
2015-01-19 10:32:27 +00:00
|
|
|
|
extern void fill_pressures(struct gas_pressures *pressures, const double amb_pressure, const struct gasmix *mix, double po2, enum dive_comp_type divemode)
|
2014-10-13 19:19:21 +00:00
|
|
|
|
{
|
|
|
|
|
if (po2) { // This is probably a CCR dive where pressures->o2 is defined
|
2014-11-01 10:06:49 +00:00
|
|
|
|
if (po2 >= amb_pressure) {
|
2014-10-13 19:19:21 +00:00
|
|
|
|
pressures->o2 = amb_pressure;
|
2014-10-14 08:46:40 +00:00
|
|
|
|
pressures->n2 = pressures->he = 0.0;
|
|
|
|
|
} else {
|
2014-10-12 18:46:41 +00:00
|
|
|
|
pressures->o2 = po2;
|
2014-12-12 11:05:01 +00:00
|
|
|
|
if (get_o2(mix) == 1000) {
|
|
|
|
|
pressures->he = pressures->n2 = 0;
|
|
|
|
|
} else {
|
|
|
|
|
pressures->he = (amb_pressure - pressures->o2) * (double)get_he(mix) / (1000 - get_o2(mix));
|
|
|
|
|
pressures->n2 = amb_pressure - pressures->o2 - pressures->he;
|
|
|
|
|
}
|
2014-10-14 08:46:40 +00:00
|
|
|
|
}
|
2014-12-30 22:28:55 +00:00
|
|
|
|
} else {
|
2015-01-10 23:01:15 +00:00
|
|
|
|
if (divemode == PSCR) { /* The steady state approximation should be good enough */
|
2015-01-19 10:32:27 +00:00
|
|
|
|
pressures->o2 = get_o2(mix) / 1000.0 * amb_pressure - (1.0 - get_o2(mix) / 1000.0) * prefs.o2consumption / (prefs.bottomsac * prefs.pscr_ratio / 1000.0);
|
2015-01-28 10:35:15 +00:00
|
|
|
|
if (pressures->o2 < 0) // He's dead, Jim.
|
|
|
|
|
pressures->o2 = 0;
|
2015-01-19 19:26:14 +00:00
|
|
|
|
if (get_o2(mix) != 1000) {
|
|
|
|
|
pressures->he = (amb_pressure - pressures->o2) * get_he(mix) / (1000.0 - get_o2(mix));
|
|
|
|
|
pressures->n2 = (amb_pressure - pressures->o2) * (1000 - get_o2(mix) - get_he(mix)) / (1000.0 - get_o2(mix));
|
|
|
|
|
} else {
|
|
|
|
|
pressures->he = pressures->n2 = 0;
|
|
|
|
|
}
|
2014-12-30 22:28:55 +00:00
|
|
|
|
} else {
|
2015-01-19 10:32:27 +00:00
|
|
|
|
// Open circuit dives: no gas pressure values available, they need to be calculated
|
|
|
|
|
pressures->o2 = get_o2(mix) / 1000.0 * amb_pressure; // These calculations are also used if the CCR calculation above..
|
|
|
|
|
pressures->he = get_he(mix) / 1000.0 * amb_pressure; // ..returned a po2 of zero (i.e. o2 sensor data not resolvable)
|
|
|
|
|
pressures->n2 = (1000 - get_o2(mix) - get_he(mix)) / 1000.0 * amb_pressure;
|
2014-12-30 22:28:55 +00:00
|
|
|
|
}
|
2014-09-15 12:55:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-28 02:04:46 +00:00
|
|
|
|
/* Force an initial gaschange event to the (old) gas #0 */
|
|
|
|
|
static void add_initial_gaschange(struct dive *dive, struct divecomputer *dc)
|
|
|
|
|
{
|
|
|
|
|
struct event *ev = get_next_event(dc->events, "gaschange");
|
|
|
|
|
|
|
|
|
|
if (ev && ev->time.seconds < 30)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/* Old starting gas mix */
|
2013-11-20 19:24:36 +00:00
|
|
|
|
add_gas_switch_event(dive, dc, 0, 0);
|
2013-03-28 02:04:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-23 22:24:02 +00:00
|
|
|
|
void dc_cylinder_renumber(struct dive *dive, struct divecomputer *dc, int mapping[])
|
2013-03-28 02:04:46 +00:00
|
|
|
|
{
|
|
|
|
|
int i;
|
2014-08-17 18:26:21 +00:00
|
|
|
|
struct event *ev;
|
2013-03-28 02:04:46 +00:00
|
|
|
|
|
|
|
|
|
/* Did the first gas get remapped? Add gas switch event */
|
|
|
|
|
if (mapping[0] > 0)
|
|
|
|
|
add_initial_gaschange(dive, dc);
|
|
|
|
|
|
|
|
|
|
/* Remap the sensor indexes */
|
|
|
|
|
for (i = 0; i < dc->samples; i++) {
|
2014-02-28 04:09:57 +00:00
|
|
|
|
struct sample *s = dc->sample + i;
|
Start cleaning up sensor indexing for multiple sensors
This is a very timid start at making us actually use multiple sensors
without the magical special case for just CCR oxygen tracking.
It mainly does:
- turn the "sample->sensor" index into an array of two indexes, to
match the pressures themselves.
- get rid of dive->{oxygen_cylinder_index,diluent_cylinder_index},
since a CCR dive should now simply set the sample->sensor[] indices
correctly instead.
- in a couple of places, start actually looping over the sensors rather
than special-case the O2 case (although often the small "loops" are
just unrolled, since it's just two cases.
but in many cases we still end up only covering the zero sensor case,
because the CCR O2 sensor code coverage was fairly limited.
It's entirely possible (even likely) that this migth break some existing
case: it tries to be a fairly direct ("stupid") translation of the old
code, but unlike the preparatory patch this does actually does change
some semantics.
For example, right now the git loader code assumes that if the git save
data contains a o2pressure entry, it just hardcodes the O2 sensor index
to 1.
In fact, one issue is going to simply be that our file formats do not
have that multiple sensor format, but instead had very clearly encoded
things as being the CCR O2 pressure sensor.
But this is hopefully close to usable, and I will need feedback (and
maybe test cases) from people who have existing CCR dives with pressure
data.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-21 02:49:45 +00:00
|
|
|
|
int j;
|
2013-03-28 02:04:46 +00:00
|
|
|
|
|
Start cleaning up sensor indexing for multiple sensors
This is a very timid start at making us actually use multiple sensors
without the magical special case for just CCR oxygen tracking.
It mainly does:
- turn the "sample->sensor" index into an array of two indexes, to
match the pressures themselves.
- get rid of dive->{oxygen_cylinder_index,diluent_cylinder_index},
since a CCR dive should now simply set the sample->sensor[] indices
correctly instead.
- in a couple of places, start actually looping over the sensors rather
than special-case the O2 case (although often the small "loops" are
just unrolled, since it's just two cases.
but in many cases we still end up only covering the zero sensor case,
because the CCR O2 sensor code coverage was fairly limited.
It's entirely possible (even likely) that this migth break some existing
case: it tries to be a fairly direct ("stupid") translation of the old
code, but unlike the preparatory patch this does actually does change
some semantics.
For example, right now the git loader code assumes that if the git save
data contains a o2pressure entry, it just hardcodes the O2 sensor index
to 1.
In fact, one issue is going to simply be that our file formats do not
have that multiple sensor format, but instead had very clearly encoded
things as being the CCR O2 pressure sensor.
But this is hopefully close to usable, and I will need feedback (and
maybe test cases) from people who have existing CCR dives with pressure
data.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-21 02:49:45 +00:00
|
|
|
|
for (j = 0; j < 2; j++) {
|
|
|
|
|
int sensor;
|
|
|
|
|
|
|
|
|
|
sensor = mapping[s->sensor[j]];
|
|
|
|
|
if (sensor >= 0)
|
|
|
|
|
s->sensor[j] = sensor;
|
|
|
|
|
}
|
2013-03-28 02:04:46 +00:00
|
|
|
|
}
|
2014-08-17 18:26:21 +00:00
|
|
|
|
|
|
|
|
|
/* Remap the gas change indexes */
|
|
|
|
|
for (ev = dc->events; ev; ev = ev->next) {
|
|
|
|
|
if (!event_is_gaschange(ev))
|
|
|
|
|
continue;
|
|
|
|
|
if (ev->gas.index < 0)
|
|
|
|
|
continue;
|
|
|
|
|
ev->gas.index = mapping[ev->gas.index];
|
|
|
|
|
}
|
2013-03-28 02:04:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the cylinder indexes change (due to merging dives or deleting
|
|
|
|
|
* cylinders in the middle), we need to change the indexes in the
|
|
|
|
|
* dive computer data for this dive.
|
|
|
|
|
*
|
|
|
|
|
* Also note that we assume that the initial cylinder is cylinder 0,
|
|
|
|
|
* so if that got renamed, we need to create a fake gas change event
|
|
|
|
|
*/
|
|
|
|
|
static void cylinder_renumber(struct dive *dive, int mapping[])
|
|
|
|
|
{
|
|
|
|
|
struct divecomputer *dc;
|
2014-10-11 11:25:52 +00:00
|
|
|
|
for_each_dc (dive, dc)
|
2013-03-28 02:04:46 +00:00
|
|
|
|
dc_cylinder_renumber(dive, dc, mapping);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-28 18:25:42 +00:00
|
|
|
|
int same_gasmix(struct gasmix *a, struct gasmix *b)
|
2017-02-08 19:36:08 +00:00
|
|
|
|
{
|
|
|
|
|
if (gasmix_is_air(a) && gasmix_is_air(b))
|
|
|
|
|
return 1;
|
|
|
|
|
return a->o2.permille == b->o2.permille && a->he.permille == b->he.permille;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-08 03:14:57 +00:00
|
|
|
|
int same_gasmix_cylinder(cylinder_t *cyl, int cylid, struct dive *dive, bool check_unused)
|
|
|
|
|
{
|
|
|
|
|
struct gasmix *mygas = &cyl->gasmix;
|
|
|
|
|
for (int i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
if (i == cylid || cylinder_none(&dive->cylinder[i]))
|
|
|
|
|
continue;
|
|
|
|
|
struct gasmix *gas2 = &dive->cylinder[i].gasmix;
|
|
|
|
|
if (gasmix_distance(mygas, gas2) == 0 && (is_cylinder_used(dive, i) || check_unused))
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-08 23:00:04 +00:00
|
|
|
|
static int pdiff(pressure_t a, pressure_t b)
|
|
|
|
|
{
|
|
|
|
|
return a.mbar && b.mbar && a.mbar != b.mbar;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int different_manual_pressures(cylinder_t *a, cylinder_t *b)
|
|
|
|
|
{
|
|
|
|
|
return pdiff(a->start, b->start) || pdiff(a->end, b->end);
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-08 19:36:08 +00:00
|
|
|
|
/*
|
|
|
|
|
* Can we find an exact match for a cylinder in another dive?
|
|
|
|
|
* Take the "already matched" map into account, so that we
|
|
|
|
|
* don't match multiple similar cylinders to one target.
|
2017-02-08 23:00:04 +00:00
|
|
|
|
*
|
|
|
|
|
* To match, the cylinders have to have the same gasmix and the
|
|
|
|
|
* same cylinder use (ie OC/Diluent/Oxygen), and if pressures
|
|
|
|
|
* have been added manually they need to match.
|
2017-02-08 19:36:08 +00:00
|
|
|
|
*/
|
|
|
|
|
static int match_cylinder(cylinder_t *cyl, struct dive *dive, unsigned int available)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
cylinder_t *target;
|
|
|
|
|
|
|
|
|
|
if (!(available & (1u << i)))
|
|
|
|
|
continue;
|
|
|
|
|
target = dive->cylinder + i;
|
|
|
|
|
if (!same_gasmix(&cyl->gasmix, &target->gasmix))
|
|
|
|
|
continue;
|
2017-02-08 23:00:04 +00:00
|
|
|
|
if (cyl->cylinder_use != target->cylinder_use)
|
|
|
|
|
continue;
|
|
|
|
|
if (different_manual_pressures(cyl, target))
|
|
|
|
|
continue;
|
2017-02-08 19:36:08 +00:00
|
|
|
|
|
|
|
|
|
/* FIXME! Should we check sizes too? */
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Note: we only allocate from the end, not in holes in the middle.
|
|
|
|
|
* So we don't look for empty bits, we look for "no more bits set".
|
|
|
|
|
* We could use some "find last bit set" math function, but let's
|
|
|
|
|
* not be fancy.
|
|
|
|
|
*/
|
|
|
|
|
static int find_unused_cylinder(unsigned int used_map)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
if (!used_map)
|
|
|
|
|
return i;
|
|
|
|
|
used_map >>= 1;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-08 23:00:04 +00:00
|
|
|
|
/*
|
|
|
|
|
* We matched things up so that they have the same gasmix and
|
|
|
|
|
* use, but we might want to fill in any missing cylinder details
|
|
|
|
|
* in 'a' if we had it from 'b'.
|
|
|
|
|
*/
|
|
|
|
|
static void merge_one_cylinder(cylinder_t *a, cylinder_t *b)
|
|
|
|
|
{
|
|
|
|
|
if (!a->type.size.mliter)
|
|
|
|
|
a->type.size.mliter = b->type.size.mliter;
|
|
|
|
|
if (!a->type.workingpressure.mbar)
|
|
|
|
|
a->type.workingpressure.mbar = b->type.workingpressure.mbar;
|
|
|
|
|
if (!a->type.description && b->type.description)
|
|
|
|
|
a->type.description = strdup(b->type.description);
|
|
|
|
|
if (!a->start.mbar)
|
|
|
|
|
a->start.mbar = b->start.mbar;
|
|
|
|
|
if (!a->end.mbar)
|
|
|
|
|
a->end.mbar = b->end.mbar;
|
2017-09-30 12:21:26 +00:00
|
|
|
|
|
|
|
|
|
if (a->sample_start.mbar && b->sample_start.mbar)
|
|
|
|
|
a->sample_start.mbar = a->sample_start.mbar > b->sample_start.mbar ? a->sample_start.mbar : b->sample_start.mbar;
|
|
|
|
|
if (a->sample_end.mbar && b->sample_end.mbar)
|
|
|
|
|
a->sample_end.mbar = a->sample_end.mbar < b->sample_end.mbar ? a->sample_end.mbar : b->sample_end.mbar;
|
2017-02-08 23:00:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-03-28 02:04:46 +00:00
|
|
|
|
/*
|
|
|
|
|
* Merging cylinder information is non-trivial, because the two dive computers
|
|
|
|
|
* may have different ideas of what the different cylinder indexing is.
|
|
|
|
|
*
|
|
|
|
|
* Logic: take all the cylinder information from the preferred dive ('a'), and
|
|
|
|
|
* then try to match each of the cylinders in the other dive by the gasmix that
|
|
|
|
|
* is the best match and hasn't been used yet.
|
|
|
|
|
*/
|
|
|
|
|
static void merge_cylinders(struct dive *res, struct dive *a, struct dive *b)
|
|
|
|
|
{
|
2017-03-11 16:43:24 +00:00
|
|
|
|
int i, renumber = 0;
|
2013-03-28 02:04:46 +00:00
|
|
|
|
int mapping[MAX_CYLINDERS];
|
2017-02-08 19:36:08 +00:00
|
|
|
|
unsigned int used_in_a = 0, used_in_b = 0, matched = 0;
|
|
|
|
|
|
|
|
|
|
/* Calculate usage map of cylinders */
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
if (!cylinder_none(a->cylinder+i) || is_cylinder_used(a, i))
|
|
|
|
|
used_in_a |= 1u << i;
|
|
|
|
|
if (!cylinder_none(b->cylinder+i) || is_cylinder_used(b, i))
|
|
|
|
|
used_in_b |= 1u << i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* For each cylinder in 'b', try to match up things */
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
int j;
|
2013-03-28 02:04:46 +00:00
|
|
|
|
|
2017-02-08 19:36:08 +00:00
|
|
|
|
mapping[i] = -1;
|
|
|
|
|
if (!(used_in_b & (1u << i)))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
j = match_cylinder(b->cylinder+i, a, used_in_a & ~matched);
|
|
|
|
|
if (j < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If we had a successful match, we:
|
|
|
|
|
*
|
2017-02-08 23:00:04 +00:00
|
|
|
|
* - try to merge individual cylinder data from both cases
|
|
|
|
|
*
|
2017-02-08 19:36:08 +00:00
|
|
|
|
* - save that in the mapping table
|
|
|
|
|
*
|
|
|
|
|
* - mark it as matched so that another cylinder in 'b'
|
|
|
|
|
* will no longer match
|
|
|
|
|
*
|
|
|
|
|
* - mark 'b' as needing renumbering if the index changed
|
|
|
|
|
*/
|
2017-02-08 23:00:04 +00:00
|
|
|
|
merge_one_cylinder(a->cylinder + j, b->cylinder + i);
|
2017-02-08 19:36:08 +00:00
|
|
|
|
mapping[i] = j;
|
|
|
|
|
matched |= 1u << j;
|
|
|
|
|
if (j != i)
|
|
|
|
|
renumber = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Consider all the cylinders we matched as used, whether they
|
|
|
|
|
* originally were or not (either in 'a' or 'b').
|
|
|
|
|
*/
|
|
|
|
|
used_in_a |= matched;
|
|
|
|
|
|
|
|
|
|
/* Now copy all the cylinder info raw from 'a' (whether used/matched or not) */
|
2013-03-28 02:04:46 +00:00
|
|
|
|
memcpy(res->cylinder, a->cylinder, sizeof(res->cylinder));
|
|
|
|
|
memset(a->cylinder, 0, sizeof(a->cylinder));
|
|
|
|
|
|
2017-02-08 19:36:08 +00:00
|
|
|
|
/*
|
|
|
|
|
* Go back to 'b' and remap any remaining cylinders that didn't
|
|
|
|
|
* match completely.
|
|
|
|
|
*/
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
|
int j;
|
|
|
|
|
|
|
|
|
|
/* Already remapped, or not interesting? */
|
|
|
|
|
if (mapping[i] >= 0)
|
|
|
|
|
continue;
|
|
|
|
|
if (!(used_in_b & (1u << i)))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
j = find_unused_cylinder(used_in_a);
|
|
|
|
|
if (j < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
res->cylinder[j] = b->cylinder[i];
|
|
|
|
|
memset(b->cylinder+i, 0, sizeof(cylinder_t));
|
|
|
|
|
mapping[i] = j;
|
|
|
|
|
used_in_a |= 1u << j;
|
|
|
|
|
if (i != j)
|
|
|
|
|
renumber = 1;
|
2017-02-02 14:31:52 +00:00
|
|
|
|
}
|
2017-02-08 19:36:08 +00:00
|
|
|
|
|
|
|
|
|
if (renumber)
|
|
|
|
|
cylinder_renumber(b, mapping);
|
2013-03-28 02:04:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-10-29 18:27:14 +00:00
|
|
|
|
static void merge_equipment(struct dive *res, struct dive *a, struct dive *b)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
2013-03-28 02:04:46 +00:00
|
|
|
|
merge_cylinders(res, a, b);
|
2012-10-29 18:27:14 +00:00
|
|
|
|
for (i = 0; i < MAX_WEIGHTSYSTEMS; i++)
|
2014-02-28 04:09:57 +00:00
|
|
|
|
merge_weightsystem_info(res->weightsystem + i, a->weightsystem + i, b->weightsystem + i);
|
2012-10-29 18:27:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-19 20:56:54 +00:00
|
|
|
|
static void merge_temperatures(struct dive *res, struct dive *a, struct dive *b)
|
2013-02-14 17:44:18 +00:00
|
|
|
|
{
|
|
|
|
|
un_fixup_airtemp(a);
|
|
|
|
|
un_fixup_airtemp(b);
|
|
|
|
|
MERGE_NONZERO(res, a, b, airtemp.mkelvin);
|
2017-02-19 20:56:54 +00:00
|
|
|
|
MERGE_NONZERO(res, a, b, watertemp.mkelvin);
|
2013-02-14 17:44:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-09 18:46:39 +00:00
|
|
|
|
/*
|
|
|
|
|
* When merging two dives, this picks the trip from one, and removes it
|
|
|
|
|
* from the other.
|
|
|
|
|
*
|
|
|
|
|
* The 'next' dive is not involved in the dive merging, but is the dive
|
|
|
|
|
* that will be the next dive after the merged dive.
|
|
|
|
|
*/
|
2012-11-11 06:49:19 +00:00
|
|
|
|
static void pick_trip(struct dive *res, struct dive *pick)
|
2012-11-09 18:46:39 +00:00
|
|
|
|
{
|
|
|
|
|
tripflag_t tripflag = pick->tripflag;
|
|
|
|
|
dive_trip_t *trip = pick->divetrip;
|
|
|
|
|
|
|
|
|
|
res->tripflag = tripflag;
|
2012-11-10 18:51:03 +00:00
|
|
|
|
add_dive_to_trip(res, trip);
|
2012-11-09 18:46:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Pick a trip for a dive
|
|
|
|
|
*/
|
2012-11-11 06:20:05 +00:00
|
|
|
|
static void merge_trip(struct dive *res, struct dive *a, struct dive *b)
|
2012-11-09 18:46:39 +00:00
|
|
|
|
{
|
2012-11-26 00:51:55 +00:00
|
|
|
|
dive_trip_t *atrip, *btrip;
|
|
|
|
|
|
2012-11-09 18:46:39 +00:00
|
|
|
|
/*
|
|
|
|
|
* The larger tripflag is more relevant: we prefer
|
|
|
|
|
* take manually assigned trips over auto-generated
|
|
|
|
|
* ones.
|
|
|
|
|
*/
|
|
|
|
|
if (a->tripflag > b->tripflag)
|
|
|
|
|
goto pick_a;
|
|
|
|
|
|
|
|
|
|
if (a->tripflag < b->tripflag)
|
|
|
|
|
goto pick_b;
|
|
|
|
|
|
2012-11-26 00:51:55 +00:00
|
|
|
|
/* Otherwise, look at the trip data and pick the "better" one */
|
|
|
|
|
atrip = a->divetrip;
|
|
|
|
|
btrip = b->divetrip;
|
|
|
|
|
if (!atrip)
|
|
|
|
|
goto pick_b;
|
|
|
|
|
if (!btrip)
|
|
|
|
|
goto pick_a;
|
|
|
|
|
if (!atrip->location)
|
2012-11-09 18:46:39 +00:00
|
|
|
|
goto pick_b;
|
2012-11-26 00:51:55 +00:00
|
|
|
|
if (!btrip->location)
|
2012-11-09 18:46:39 +00:00
|
|
|
|
goto pick_a;
|
2012-11-26 00:51:55 +00:00
|
|
|
|
if (!atrip->notes)
|
2012-11-09 18:46:39 +00:00
|
|
|
|
goto pick_b;
|
2012-11-26 00:51:55 +00:00
|
|
|
|
if (!btrip->notes)
|
2012-11-09 18:46:39 +00:00
|
|
|
|
goto pick_a;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Ok, so both have location and notes.
|
|
|
|
|
* Pick the earlier one.
|
|
|
|
|
*/
|
|
|
|
|
if (a->when < b->when)
|
|
|
|
|
goto pick_a;
|
|
|
|
|
goto pick_b;
|
|
|
|
|
|
|
|
|
|
pick_a:
|
2012-11-11 06:49:19 +00:00
|
|
|
|
b = a;
|
2012-11-09 18:46:39 +00:00
|
|
|
|
pick_b:
|
2012-11-11 06:49:19 +00:00
|
|
|
|
pick_trip(res, b);
|
2012-11-09 18:46:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-25 23:10:03 +00:00
|
|
|
|
#if CURRENTLY_NOT_USED
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
/*
|
|
|
|
|
* Sample 's' is between samples 'a' and 'b'. It is 'offset' seconds before 'b'.
|
|
|
|
|
*
|
|
|
|
|
* If 's' and 'a' are at the same time, offset is 0, and b is NULL.
|
|
|
|
|
*/
|
|
|
|
|
static int compare_sample(struct sample *s, struct sample *a, struct sample *b, int offset)
|
|
|
|
|
{
|
|
|
|
|
unsigned int depth = a->depth.mm;
|
|
|
|
|
int diff;
|
|
|
|
|
|
|
|
|
|
if (offset) {
|
|
|
|
|
unsigned int interval = b->time.seconds - a->time.seconds;
|
|
|
|
|
unsigned int depth_a = a->depth.mm;
|
|
|
|
|
unsigned int depth_b = b->depth.mm;
|
|
|
|
|
|
|
|
|
|
if (offset > interval)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
/* pick the average depth, scaled by the offset from 'b' */
|
|
|
|
|
depth = (depth_a * offset) + (depth_b * (interval - offset));
|
|
|
|
|
depth /= interval;
|
|
|
|
|
}
|
|
|
|
|
diff = s->depth.mm - depth;
|
|
|
|
|
if (diff < 0)
|
|
|
|
|
diff = -diff;
|
|
|
|
|
/* cut off at one meter difference */
|
|
|
|
|
if (diff > 1000)
|
|
|
|
|
diff = 1000;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
return diff * diff;
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Calculate a "difference" in samples between the two dives, given
|
|
|
|
|
* the offset in seconds between them. Use this to find the best
|
|
|
|
|
* match of samples between two different dive computers.
|
|
|
|
|
*/
|
2012-11-24 02:51:27 +00:00
|
|
|
|
static unsigned long sample_difference(struct divecomputer *a, struct divecomputer *b, int offset)
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
{
|
|
|
|
|
int asamples = a->samples;
|
|
|
|
|
int bsamples = b->samples;
|
|
|
|
|
struct sample *as = a->sample;
|
|
|
|
|
struct sample *bs = b->sample;
|
|
|
|
|
unsigned long error = 0;
|
|
|
|
|
int start = -1;
|
|
|
|
|
|
|
|
|
|
if (!asamples || !bsamples)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* skip the first sample - this way we know can always look at
|
|
|
|
|
* as/bs[-1] to look at the samples around it in the loop.
|
|
|
|
|
*/
|
2014-02-28 04:09:57 +00:00
|
|
|
|
as++;
|
|
|
|
|
bs++;
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
asamples--;
|
|
|
|
|
bsamples--;
|
|
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
int at, bt, diff;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* If we run out of samples, punt */
|
|
|
|
|
if (!asamples)
|
|
|
|
|
return INT_MAX;
|
|
|
|
|
if (!bsamples)
|
|
|
|
|
return INT_MAX;
|
|
|
|
|
|
|
|
|
|
at = as->time.seconds;
|
|
|
|
|
bt = bs->time.seconds + offset;
|
|
|
|
|
|
|
|
|
|
/* b hasn't started yet? Ignore it */
|
|
|
|
|
if (bt < 0) {
|
|
|
|
|
bs++;
|
|
|
|
|
bsamples--;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (at < bt) {
|
2014-02-28 04:09:57 +00:00
|
|
|
|
diff = compare_sample(as, bs - 1, bs, bt - at);
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
as++;
|
|
|
|
|
asamples--;
|
|
|
|
|
} else if (at > bt) {
|
2014-02-28 04:09:57 +00:00
|
|
|
|
diff = compare_sample(bs, as - 1, as, at - bt);
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
bs++;
|
|
|
|
|
bsamples--;
|
|
|
|
|
} else {
|
|
|
|
|
diff = compare_sample(as, bs, NULL, 0);
|
2014-02-28 04:09:57 +00:00
|
|
|
|
as++;
|
|
|
|
|
bs++;
|
|
|
|
|
asamples--;
|
|
|
|
|
bsamples--;
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Invalid comparison point? */
|
|
|
|
|
if (diff < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (start < 0)
|
|
|
|
|
start = at;
|
|
|
|
|
|
|
|
|
|
error += diff;
|
|
|
|
|
|
|
|
|
|
if (at - start > 120)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Dive 'a' is 'offset' seconds before dive 'b'
|
|
|
|
|
*
|
|
|
|
|
* This is *not* because the dive computers clocks aren't in sync,
|
|
|
|
|
* it is because the dive computers may "start" the dive at different
|
|
|
|
|
* points in the dive, so the sample at time X in dive 'a' is the
|
|
|
|
|
* same as the sample at time X+offset in dive 'b'.
|
|
|
|
|
*
|
|
|
|
|
* For example, some dive computers take longer to "wake up" when
|
|
|
|
|
* they sense that you are under water (ie Uemis Zurich if it was off
|
|
|
|
|
* when the dive started). And other dive computers have different
|
|
|
|
|
* depths that they activate at, etc etc.
|
|
|
|
|
*
|
|
|
|
|
* If we cannot find a shared offset, don't try to merge.
|
|
|
|
|
*/
|
2012-11-24 02:51:27 +00:00
|
|
|
|
static int find_sample_offset(struct divecomputer *a, struct divecomputer *b)
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
{
|
|
|
|
|
int offset, best;
|
|
|
|
|
unsigned long max;
|
|
|
|
|
|
|
|
|
|
/* No samples? Merge at any time (0 offset) */
|
|
|
|
|
if (!a->samples)
|
|
|
|
|
return 0;
|
|
|
|
|
if (!b->samples)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Common special-case: merging a dive that came from
|
|
|
|
|
* the same dive computer, so the samples are identical.
|
|
|
|
|
* Check this first, without wasting time trying to find
|
|
|
|
|
* some minimal offset case.
|
|
|
|
|
*/
|
|
|
|
|
best = 0;
|
|
|
|
|
max = sample_difference(a, b, 0);
|
|
|
|
|
if (!max)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Otherwise, look if we can find anything better within
|
|
|
|
|
* a thirty second window..
|
|
|
|
|
*/
|
|
|
|
|
for (offset = -30; offset <= 30; offset++) {
|
|
|
|
|
unsigned long diff;
|
|
|
|
|
|
|
|
|
|
diff = sample_difference(a, b, offset);
|
|
|
|
|
if (diff > max)
|
|
|
|
|
continue;
|
|
|
|
|
best = offset;
|
|
|
|
|
max = diff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return best;
|
|
|
|
|
}
|
2012-11-25 23:10:03 +00:00
|
|
|
|
#endif
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
/*
|
|
|
|
|
* Are a and b "similar" values, when given a reasonable lower end expected
|
|
|
|
|
* difference?
|
|
|
|
|
*
|
|
|
|
|
* So for example, we'd expect different dive computers to give different
|
2017-03-06 12:27:39 +00:00
|
|
|
|
* max. depth readings. You might have them on different arms, and they
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
* have different pressure sensors and possibly different ideas about
|
|
|
|
|
* water salinity etc.
|
|
|
|
|
*
|
|
|
|
|
* So have an expected minimum difference, but also allow a larger relative
|
|
|
|
|
* error value.
|
|
|
|
|
*/
|
|
|
|
|
static int similar(unsigned long a, unsigned long b, unsigned long expected)
|
|
|
|
|
{
|
2016-02-04 19:16:04 +00:00
|
|
|
|
if (!a && !b)
|
|
|
|
|
return 1;
|
|
|
|
|
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
if (a && b) {
|
|
|
|
|
unsigned long min, max, diff;
|
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
min = a;
|
|
|
|
|
max = b;
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
if (a > b) {
|
|
|
|
|
min = b;
|
|
|
|
|
max = a;
|
|
|
|
|
}
|
|
|
|
|
diff = max - min;
|
|
|
|
|
|
|
|
|
|
/* Smaller than expected difference? */
|
|
|
|
|
if (diff < expected)
|
|
|
|
|
return 1;
|
|
|
|
|
/* Error less than 10% or the maximum */
|
2014-02-28 04:09:57 +00:00
|
|
|
|
if (diff * 10 < max)
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-16 18:55:58 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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(struct divecomputer *a, struct divecomputer *b)
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
{
|
2012-12-16 18:55:58 +00:00
|
|
|
|
/* Not same model? Don't know if matching.. */
|
|
|
|
|
if (!a->model || !b->model)
|
|
|
|
|
return 0;
|
|
|
|
|
if (strcasecmp(a->model, b->model))
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
return 0;
|
2012-12-16 18:55:58 +00:00
|
|
|
|
|
|
|
|
|
/* 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 ? 1 : -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Match every dive computer against each other to see if
|
|
|
|
|
* we have a matching dive.
|
|
|
|
|
*
|
|
|
|
|
* Return values:
|
|
|
|
|
* -1 for "is definitely *NOT* the same dive"
|
|
|
|
|
* 0 for "don't know"
|
|
|
|
|
* 1 for "is definitely the same dive"
|
|
|
|
|
*/
|
|
|
|
|
static int match_dc_dive(struct divecomputer *a, struct divecomputer *b)
|
|
|
|
|
{
|
|
|
|
|
do {
|
|
|
|
|
struct divecomputer *tmp = b;
|
|
|
|
|
do {
|
|
|
|
|
int match = match_one_dc(a, tmp);
|
|
|
|
|
if (match)
|
|
|
|
|
return match;
|
|
|
|
|
tmp = tmp->next;
|
|
|
|
|
} while (tmp);
|
|
|
|
|
a = a->next;
|
|
|
|
|
} while (a);
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-24 20:59:11 +00:00
|
|
|
|
static bool new_without_trip(struct dive *a)
|
|
|
|
|
{
|
|
|
|
|
return a->downloaded && !a->divetrip;
|
|
|
|
|
}
|
|
|
|
|
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
/*
|
|
|
|
|
* Do we want to automatically try to merge two dives that
|
|
|
|
|
* look like they are the same dive?
|
|
|
|
|
*
|
|
|
|
|
* This happens quite commonly because you download a dive
|
|
|
|
|
* that you already had, or perhaps because you maintained
|
|
|
|
|
* multiple dive logs and want to load them all together
|
|
|
|
|
* (possibly one of them was imported from another dive log
|
|
|
|
|
* application entirely).
|
|
|
|
|
*
|
|
|
|
|
* NOTE! We mainly look at the dive time, but it can differ
|
|
|
|
|
* between two dives due to a few issues:
|
|
|
|
|
*
|
|
|
|
|
* - rounding the dive date to the nearest minute in other dive
|
|
|
|
|
* applications
|
|
|
|
|
*
|
|
|
|
|
* - dive computers with "relative datestamps" (ie the dive
|
|
|
|
|
* computer doesn't actually record an absolute date at all,
|
2015-11-15 18:15:40 +00:00
|
|
|
|
* but instead at download-time synchronizes its internal
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
* time with real-time on the downloading computer)
|
|
|
|
|
*
|
|
|
|
|
* - using multiple dive computers with different real time on
|
|
|
|
|
* the same dive
|
|
|
|
|
*
|
|
|
|
|
* We do not merge dives that look radically different, and if
|
|
|
|
|
* the dates are *too* far off the user will have to join two
|
|
|
|
|
* dives together manually. But this tries to handle the sane
|
|
|
|
|
* cases.
|
|
|
|
|
*/
|
|
|
|
|
static int likely_same_dive(struct dive *a, struct dive *b)
|
|
|
|
|
{
|
2013-01-23 19:53:42 +00:00
|
|
|
|
int match, fuzz = 20 * 60;
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
|
2015-09-30 11:45:55 +00:00
|
|
|
|
/* don't merge manually added dives with anything */
|
|
|
|
|
if (same_string(a->dc.model, "manually added dive") ||
|
|
|
|
|
same_string(b->dc.model, "manually added dive"))
|
|
|
|
|
return 0;
|
|
|
|
|
|
2014-07-24 20:59:11 +00:00
|
|
|
|
/* Don't try to merge dives with different trip information */
|
|
|
|
|
if (a->divetrip != b->divetrip) {
|
|
|
|
|
/*
|
|
|
|
|
* Exception: if the dive is downloaded without any
|
|
|
|
|
* explicit trip information, we do want to merge it
|
|
|
|
|
* with existing old dives even if they have trips.
|
|
|
|
|
*/
|
|
|
|
|
if (!new_without_trip(a) && !new_without_trip(b))
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
Allow overlapping (and disjoint) dive trips
We used to have the rule that a dive trip has to have all dives in it in
sequential order, even though our XML file really is much more flexible,
and allows arbitrary nesting of dives within a dive trip.
Put another way, the old model had fairly inflexible rules:
- the dive array is sorted by time
- a dive trip is always a contiguous slice of this sorted array
which makes perfect sense when you think of the dive and trip list as a
physical activity by one person, but leads to various very subtle issues
in the general case when there are no guarantees that the user then uses
subsurface that way.
In particular, if you load the XML files of two divers that have
overlapping dive trips, the end result is incredibly messy, and does not
conform to the above model at all.
There's two ways to enforce such conformance:
- disallow that kind of behavior entirely.
This is actually hard. Our XML files aren't date-based, they are
based on XML nesting rules, and even a single XML file can have
nesting that violates the date ordering. With multiple XML files,
it's trivial to do in practice, and while we could just fail at
loading, the failure would have to be a hard failure that leaves the
user no way to use the data at all.
- try to "fix it up" by sorting, splitting, and combining dive trips
automatically.
Dirk had a patch to do this, but it really does destroy the actual
dive data: if you load both mine and Dirk's dive trips, you ended up
with a result that followed the above two technical rules, but that
didn't actually make any *sense*.
So this patch doesn't try to enforce the rules, and instead just changes
them to be more generic:
- the dive array is still sorted by dive time
- a dive trip is just an arbitrary collection of dives.
The relaxed rules means that mixing dives and dive trips for two people
is trivial, and we can easily handle any XML file. The dive trip is
defined by the XML nesting level, and is totally independent of any
date-based sorting.
It does require a few things:
- when we save our dive data, we have to do it hierarchically by dive
trip, not just by walking the dive array linearly.
- similarly, when we create the dive tree model, we can't just blindly
walk the array of dives one by one, we have to look up the correct
trip (parent)
- when we try to merge two dives that are adjacent (by date sorting),
we can't do it if they are in different trips.
but apart from that, nothing else really changes.
NOTE! Despite the new relaxed model, creating totally disjoing dive
trips is not all that easy (nor is there any *reason* for it to be
easty). Our GUI interfaces still are "add dive to trip above" etc, and
the automatic adding of dives to dive trips is obviously still based on
date.
So this does not really change the expected normal usage, the relaxed
data structure rules just mean that we don't need to worry about the odd
cases as much, because we can just let them be.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
|
|
|
|
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
/*
|
|
|
|
|
* Do some basic sanity testing of the values we
|
|
|
|
|
* have filled in during 'fixup_dive()'
|
|
|
|
|
*/
|
2013-02-09 15:12:30 +00:00
|
|
|
|
if (!similar(a->maxdepth.mm, b->maxdepth.mm, 1000) ||
|
2013-02-16 17:04:03 +00:00
|
|
|
|
(a->meandepth.mm && b->meandepth.mm && !similar(a->meandepth.mm, b->meandepth.mm, 1000)) ||
|
2017-09-10 08:39:25 +00:00
|
|
|
|
!a->duration.seconds || !b->duration.seconds ||
|
2014-02-28 04:09:57 +00:00
|
|
|
|
!similar(a->duration.seconds, b->duration.seconds, 5 * 60))
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
return 0;
|
|
|
|
|
|
2012-12-16 18:55:58 +00:00
|
|
|
|
/* See if we can get an exact match on the dive computer */
|
|
|
|
|
match = match_dc_dive(&a->dc, &b->dc);
|
|
|
|
|
if (match)
|
|
|
|
|
return match > 0;
|
|
|
|
|
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
/*
|
2012-12-16 18:55:58 +00:00
|
|
|
|
* Allow a time difference due to dive computer time
|
|
|
|
|
* setting etc. Check if they overlap.
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
*/
|
2013-02-09 15:12:30 +00:00
|
|
|
|
fuzz = MAX(a->duration.seconds, b->duration.seconds) / 2;
|
2012-12-16 18:55:58 +00:00
|
|
|
|
if (fuzz < 60)
|
|
|
|
|
fuzz = 60;
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
|
|
|
|
|
return ((a->when <= b->when + fuzz) && (a->when >= b->when - fuzz));
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
|
/*
|
|
|
|
|
* This could do a lot more merging. Right now it really only
|
|
|
|
|
* merges almost exact duplicates - something that happens easily
|
|
|
|
|
* with overlapping dive downloads.
|
|
|
|
|
*/
|
2013-10-05 07:29:09 +00:00
|
|
|
|
struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded)
|
2011-09-03 20:19:26 +00:00
|
|
|
|
{
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
if (likely_same_dive(a, b))
|
|
|
|
|
return merge_dives(a, b, 0, prefer_downloaded);
|
|
|
|
|
return NULL;
|
2012-11-25 04:29:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-23 22:24:02 +00:00
|
|
|
|
void free_events(struct event *ev)
|
2012-11-25 04:29:14 +00:00
|
|
|
|
{
|
|
|
|
|
while (ev) {
|
|
|
|
|
struct event *next = ev->next;
|
|
|
|
|
free(ev);
|
|
|
|
|
ev = next;
|
Add special download modes to force updates from the divecomputer
This will hopefully not be something we need often, but if we improve
support for a divecomputer (either in libdivecomputer or in our native
Uemis code or even in the way we handle (and potentially discard) events),
then it is extremely useful to be able to say "re-download things
from the divecomputer and for things that were not edited in Subsurface,
don't try to merge the data (which gives BAD results if for example you
fixed a bug in the depth calculation in libdivecomputer) but instead
simply take the samples, the events and some of the other unedited data
straight from the download".
This commit implements just that - a "force download" checkbox in the
download dialog that makes us reimport all dives from the dive computer,
even the ones we already have, and an "always prefer downloaded dive"
checkbox that then tells Subsurface not to merge but simply to take the
data from the downloaded dive - without overwriting the things we have
already edited in Subsurface (like location, buddy, equipment, etc).
This, as a precaution, refuses to merge dives that don't have identical
start times. So if you have edited the date / time of a dive or if you
have previously merged your dive with a different dive computer (and
therefore modified samples and events) you are out of luck.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-11 13:29:26 +00:00
|
|
|
|
}
|
2012-11-25 04:29:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
Make 'clear_dive()' free the primary dive computer data properly
Our primary dive computer really is special, not just because it's the
first one: it's directly embedded in the "struct dive", and so if you
just walk the divecomputer list, you'll miss it, because it's not _on_
the list, it is the very head _of_ the list.
We had that bug in copy_dive(), and it turns out we have it in
clear_dive() too: clear_dive() would free all the dive computers on the
list, but not the actual primary one.
This is a minor memory leak, no more, so it's not exactly critial, but
let's just do it right.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-11 21:25:03 +00:00
|
|
|
|
static void free_dc_contents(struct divecomputer *dc)
|
2012-11-25 04:29:14 +00:00
|
|
|
|
{
|
|
|
|
|
free(dc->sample);
|
2014-05-12 17:58:15 +00:00
|
|
|
|
free((void *)dc->model);
|
2012-11-25 04:29:14 +00:00
|
|
|
|
free_events(dc->events);
|
Make 'clear_dive()' free the primary dive computer data properly
Our primary dive computer really is special, not just because it's the
first one: it's directly embedded in the "struct dive", and so if you
just walk the divecomputer list, you'll miss it, because it's not _on_
the list, it is the very head _of_ the list.
We had that bug in copy_dive(), and it turns out we have it in
clear_dive() too: clear_dive() would free all the dive computers on the
list, but not the actual primary one.
This is a minor memory leak, no more, so it's not exactly critial, but
let's just do it right.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-04-11 21:25:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void free_dc(struct divecomputer *dc)
|
|
|
|
|
{
|
|
|
|
|
free_dc_contents(dc);
|
2012-11-25 04:29:14 +00:00
|
|
|
|
free(dc);
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-02 22:29:02 +00:00
|
|
|
|
static void free_pic(struct picture *picture)
|
|
|
|
|
{
|
|
|
|
|
if (picture) {
|
|
|
|
|
free(picture->filename);
|
|
|
|
|
free(picture);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-25 04:29:14 +00:00
|
|
|
|
static int same_sample(struct sample *a, struct sample *b)
|
|
|
|
|
{
|
|
|
|
|
if (a->time.seconds != b->time.seconds)
|
|
|
|
|
return 0;
|
|
|
|
|
if (a->depth.mm != b->depth.mm)
|
|
|
|
|
return 0;
|
|
|
|
|
if (a->temperature.mkelvin != b->temperature.mkelvin)
|
|
|
|
|
return 0;
|
2017-07-20 21:39:02 +00:00
|
|
|
|
if (a->pressure[0].mbar != b->pressure[0].mbar)
|
2012-11-25 04:29:14 +00:00
|
|
|
|
return 0;
|
Start cleaning up sensor indexing for multiple sensors
This is a very timid start at making us actually use multiple sensors
without the magical special case for just CCR oxygen tracking.
It mainly does:
- turn the "sample->sensor" index into an array of two indexes, to
match the pressures themselves.
- get rid of dive->{oxygen_cylinder_index,diluent_cylinder_index},
since a CCR dive should now simply set the sample->sensor[] indices
correctly instead.
- in a couple of places, start actually looping over the sensors rather
than special-case the O2 case (although often the small "loops" are
just unrolled, since it's just two cases.
but in many cases we still end up only covering the zero sensor case,
because the CCR O2 sensor code coverage was fairly limited.
It's entirely possible (even likely) that this migth break some existing
case: it tries to be a fairly direct ("stupid") translation of the old
code, but unlike the preparatory patch this does actually does change
some semantics.
For example, right now the git loader code assumes that if the git save
data contains a o2pressure entry, it just hardcodes the O2 sensor index
to 1.
In fact, one issue is going to simply be that our file formats do not
have that multiple sensor format, but instead had very clearly encoded
things as being the CCR O2 pressure sensor.
But this is hopefully close to usable, and I will need feedback (and
maybe test cases) from people who have existing CCR dives with pressure
data.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2017-07-21 02:49:45 +00:00
|
|
|
|
return a->sensor[0] == b->sensor[0];
|
2012-11-25 04:29:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int same_dc(struct divecomputer *a, struct divecomputer *b)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
struct event *eva, *evb;
|
|
|
|
|
|
2012-12-16 20:33:37 +00:00
|
|
|
|
i = match_one_dc(a, b);
|
|
|
|
|
if (i)
|
|
|
|
|
return i > 0;
|
|
|
|
|
|
2012-11-25 04:29:14 +00:00
|
|
|
|
if (a->when && b->when && a->when != b->when)
|
|
|
|
|
return 0;
|
|
|
|
|
if (a->samples != b->samples)
|
|
|
|
|
return 0;
|
|
|
|
|
for (i = 0; i < a->samples; i++)
|
2014-02-28 04:09:57 +00:00
|
|
|
|
if (!same_sample(a->sample + i, b->sample + i))
|
2012-11-25 04:29:14 +00:00
|
|
|
|
return 0;
|
|
|
|
|
eva = a->events;
|
|
|
|
|
evb = b->events;
|
|
|
|
|
while (eva && evb) {
|
|
|
|
|
if (!same_event(eva, evb))
|
|
|
|
|
return 0;
|
|
|
|
|
eva = eva->next;
|
|
|
|
|
evb = evb->next;
|
|
|
|
|
}
|
|
|
|
|
return eva == evb;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-23 00:57:07 +00:00
|
|
|
|
static int might_be_same_device(struct divecomputer *a, struct divecomputer *b)
|
|
|
|
|
{
|
|
|
|
|
/* No dive computer model? That matches anything */
|
|
|
|
|
if (!a->model || !b->model)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
/* Otherwise at least the model names have to match */
|
|
|
|
|
if (strcasecmp(a->model, b->model))
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
/* No device ID? Match */
|
|
|
|
|
if (!a->deviceid || !b->deviceid)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
return a->deviceid == b->deviceid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void remove_redundant_dc(struct divecomputer *dc, int prefer_downloaded)
|
2012-11-25 04:29:14 +00:00
|
|
|
|
{
|
|
|
|
|
do {
|
|
|
|
|
struct divecomputer **p = &dc->next;
|
|
|
|
|
|
|
|
|
|
/* Check this dc against all the following ones.. */
|
|
|
|
|
while (*p) {
|
|
|
|
|
struct divecomputer *check = *p;
|
2013-01-23 00:57:07 +00:00
|
|
|
|
if (same_dc(dc, check) || (prefer_downloaded && might_be_same_device(dc, check))) {
|
2012-11-25 04:29:14 +00:00
|
|
|
|
*p = check->next;
|
|
|
|
|
check->next = NULL;
|
|
|
|
|
free_dc(check);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
p = &check->next;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-23 00:57:07 +00:00
|
|
|
|
/* .. and then continue down the chain, but we */
|
|
|
|
|
prefer_downloaded = 0;
|
2012-11-25 04:29:14 +00:00
|
|
|
|
dc = dc->next;
|
|
|
|
|
} while (dc);
|
2012-11-11 06:20:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-25 20:39:08 +00:00
|
|
|
|
static void clear_dc(struct divecomputer *dc)
|
|
|
|
|
{
|
|
|
|
|
memset(dc, 0, sizeof(*dc));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct divecomputer *find_matching_computer(struct divecomputer *match, struct divecomputer *list)
|
|
|
|
|
{
|
|
|
|
|
struct divecomputer *p;
|
|
|
|
|
|
|
|
|
|
while ((p = list) != NULL) {
|
|
|
|
|
list = list->next;
|
|
|
|
|
|
2013-01-23 00:57:07 +00:00
|
|
|
|
if (might_be_same_device(match, p))
|
2012-11-25 20:39:08 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-04 05:21:47 +00:00
|
|
|
|
|
|
|
|
|
static void copy_dive_computer(struct divecomputer *res, struct divecomputer *a)
|
|
|
|
|
{
|
|
|
|
|
*res = *a;
|
2014-07-03 04:05:22 +00:00
|
|
|
|
res->model = copy_string(a->model);
|
2013-02-04 05:21:47 +00:00
|
|
|
|
res->samples = res->alloc_samples = 0;
|
|
|
|
|
res->sample = NULL;
|
|
|
|
|
res->events = NULL;
|
|
|
|
|
res->next = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-25 20:39:08 +00:00
|
|
|
|
/*
|
|
|
|
|
* Join dive computers with a specific time offset between
|
|
|
|
|
* them.
|
|
|
|
|
*
|
|
|
|
|
* Use the dive computer ID's (or names, if ID's are missing)
|
|
|
|
|
* to match them up. If we find a matching dive computer, we
|
|
|
|
|
* merge them. If not, we just take the data from 'a'.
|
|
|
|
|
*/
|
|
|
|
|
static void interleave_dive_computers(struct divecomputer *res,
|
2014-02-28 04:09:57 +00:00
|
|
|
|
struct divecomputer *a, struct divecomputer *b, int offset)
|
2012-11-25 20:39:08 +00:00
|
|
|
|
{
|
|
|
|
|
do {
|
|
|
|
|
struct divecomputer *match;
|
|
|
|
|
|
2013-02-04 05:21:47 +00:00
|
|
|
|
copy_dive_computer(res, a);
|
2012-11-25 20:39:08 +00:00
|
|
|
|
|
|
|
|
|
match = find_matching_computer(a, b);
|
|
|
|
|
if (match) {
|
|
|
|
|
merge_events(res, a, match, offset);
|
|
|
|
|
merge_samples(res, a, match, offset);
|
2014-05-20 05:01:40 +00:00
|
|
|
|
/* Use the diveid of the later dive! */
|
|
|
|
|
if (offset > 0)
|
|
|
|
|
res->diveid = match->diveid;
|
2012-11-25 20:39:08 +00:00
|
|
|
|
} else {
|
|
|
|
|
res->sample = a->sample;
|
|
|
|
|
res->samples = a->samples;
|
|
|
|
|
res->events = a->events;
|
|
|
|
|
a->sample = NULL;
|
|
|
|
|
a->samples = 0;
|
|
|
|
|
a->events = NULL;
|
|
|
|
|
}
|
|
|
|
|
a = a->next;
|
|
|
|
|
if (!a)
|
|
|
|
|
break;
|
|
|
|
|
res->next = calloc(1, sizeof(struct divecomputer));
|
|
|
|
|
res = res->next;
|
|
|
|
|
} while (res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Join dive computer information.
|
|
|
|
|
*
|
|
|
|
|
* If we have old-style dive computer information (no model
|
|
|
|
|
* name etc), we will prefer a new-style one and just throw
|
|
|
|
|
* away the old. We're assuming it's a re-download.
|
|
|
|
|
*
|
2013-01-23 00:57:07 +00:00
|
|
|
|
* Otherwise, we'll just try to keep all the information,
|
|
|
|
|
* unless the user has specified that they prefer the
|
|
|
|
|
* downloaded computer, in which case we'll aggressively
|
|
|
|
|
* try to throw out old information that *might* be from
|
|
|
|
|
* that one.
|
2012-11-25 20:39:08 +00:00
|
|
|
|
*/
|
2013-01-23 00:57:07 +00:00
|
|
|
|
static void join_dive_computers(struct divecomputer *res, struct divecomputer *a, struct divecomputer *b, int prefer_downloaded)
|
2012-11-25 20:39:08 +00:00
|
|
|
|
{
|
2012-12-16 20:33:37 +00:00
|
|
|
|
struct divecomputer *tmp;
|
|
|
|
|
|
2012-11-25 20:39:08 +00:00
|
|
|
|
if (a->model && !b->model) {
|
|
|
|
|
*res = *a;
|
|
|
|
|
clear_dc(a);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (b->model && !a->model) {
|
|
|
|
|
*res = *b;
|
|
|
|
|
clear_dc(b);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*res = *a;
|
|
|
|
|
clear_dc(a);
|
2012-12-16 20:33:37 +00:00
|
|
|
|
tmp = res;
|
|
|
|
|
while (tmp->next)
|
|
|
|
|
tmp = tmp->next;
|
2012-11-25 20:39:08 +00:00
|
|
|
|
|
2012-12-16 20:33:37 +00:00
|
|
|
|
tmp->next = calloc(1, sizeof(*tmp));
|
|
|
|
|
*tmp->next = *b;
|
2012-11-25 20:39:08 +00:00
|
|
|
|
clear_dc(b);
|
|
|
|
|
|
2013-01-23 00:57:07 +00:00
|
|
|
|
remove_redundant_dc(res, prefer_downloaded);
|
2012-11-25 20:39:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-10 16:39:51 +00:00
|
|
|
|
static bool tag_seen_before(struct tag_entry *start, struct tag_entry *before)
|
|
|
|
|
{
|
2014-10-11 11:25:52 +00:00
|
|
|
|
while (start && start != before) {
|
2014-07-10 16:39:51 +00:00
|
|
|
|
if (same_string(start->tag->name, before->tag->name))
|
|
|
|
|
return true;
|
|
|
|
|
start = start->next;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* remove duplicates and empty nodes */
|
|
|
|
|
void taglist_cleanup(struct tag_entry **tag_list)
|
|
|
|
|
{
|
|
|
|
|
struct tag_entry **tl = tag_list;
|
|
|
|
|
while (*tl) {
|
|
|
|
|
/* skip tags that are empty or that we have seen before */
|
|
|
|
|
if (same_string((*tl)->tag->name, "") || tag_seen_before(*tag_list, *tl)) {
|
|
|
|
|
*tl = (*tl)->next;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
tl = &(*tl)->next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
int taglist_get_tagstring(struct tag_entry *tag_list, char *buffer, int len)
|
|
|
|
|
{
|
2013-11-02 01:12:42 +00:00
|
|
|
|
int i = 0;
|
|
|
|
|
struct tag_entry *tmp;
|
Get rid of crazy empty tag_list element at the start
So this is totally unrelated to the git repository format, except for
the fact that I noticed it while writing the git saving code.
The subsurface divetag list handling is being stupid, and has a
initial dummy entry at the head of the list for no good reason.
I say "no good reason", because there *is* a reason for it: it allows
code to avoid the special case of empty list and adding entries to
before the first entry etc etc. But that reason is a really *bad*
reason, because it's valid only because people don't understand basic
list manipulation and pointers to pointers.
So get rid of the dummy element, and do things right instead - by
passing a *pointer* to the list, instead of the list. And then when
traversing the list and looking for a place to insert things, don't go
to the next entry - just update the "pointer to pointer" to point to
the address of the next entry. Each entry in a C linked list is no
different than the list itself, so you can use the pointer to the
pointer to the next entry as a pointer to the list.
This is a pet peeve of mine. The real beauty of pointers can never be
understood unless you understand the indirection they allow. People
who grew up with Pascal and were corrupted by that mindset are
mentally stunted. Niklaus Wirth has a lot to answer for!
But never fear. You too can overcome that mental limitation, it just
needs some brain exercise. Reading this patch may help. In particular,
contemplate the new "taglist_add_divetag()".
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
|
|
|
|
tmp = tag_list;
|
2013-11-02 01:12:42 +00:00
|
|
|
|
memset(buffer, 0, len);
|
2014-02-28 04:09:57 +00:00
|
|
|
|
while (tmp != NULL) {
|
2013-11-02 01:12:42 +00:00
|
|
|
|
int newlength = strlen(tmp->tag->name);
|
|
|
|
|
if (i > 0)
|
|
|
|
|
newlength += 2;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
if ((i + newlength) < len) {
|
2013-11-02 01:12:42 +00:00
|
|
|
|
if (i > 0) {
|
2014-02-28 04:09:57 +00:00
|
|
|
|
strcpy(buffer + i, ", ");
|
|
|
|
|
strcpy(buffer + i + 2, tmp->tag->name);
|
2013-11-02 01:12:42 +00:00
|
|
|
|
} else {
|
|
|
|
|
strcpy(buffer, tmp->tag->name);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
i += newlength;
|
|
|
|
|
tmp = tmp->next;
|
|
|
|
|
}
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
static inline void taglist_free_divetag(struct divetag *tag)
|
|
|
|
|
{
|
2013-11-02 01:12:42 +00:00
|
|
|
|
if (tag->name != NULL)
|
|
|
|
|
free(tag->name);
|
|
|
|
|
if (tag->source != NULL)
|
|
|
|
|
free(tag->source);
|
|
|
|
|
free(tag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Add a tag to the tag_list, keep the list sorted */
|
Get rid of crazy empty tag_list element at the start
So this is totally unrelated to the git repository format, except for
the fact that I noticed it while writing the git saving code.
The subsurface divetag list handling is being stupid, and has a
initial dummy entry at the head of the list for no good reason.
I say "no good reason", because there *is* a reason for it: it allows
code to avoid the special case of empty list and adding entries to
before the first entry etc etc. But that reason is a really *bad*
reason, because it's valid only because people don't understand basic
list manipulation and pointers to pointers.
So get rid of the dummy element, and do things right instead - by
passing a *pointer* to the list, instead of the list. And then when
traversing the list and looking for a place to insert things, don't go
to the next entry - just update the "pointer to pointer" to point to
the address of the next entry. Each entry in a C linked list is no
different than the list itself, so you can use the pointer to the
pointer to the next entry as a pointer to the list.
This is a pet peeve of mine. The real beauty of pointers can never be
understood unless you understand the indirection they allow. People
who grew up with Pascal and were corrupted by that mindset are
mentally stunted. Niklaus Wirth has a lot to answer for!
But never fear. You too can overcome that mental limitation, it just
needs some brain exercise. Reading this patch may help. In particular,
contemplate the new "taglist_add_divetag()".
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
|
|
|
|
static struct divetag *taglist_add_divetag(struct tag_entry **tag_list, struct divetag *tag)
|
|
|
|
|
{
|
|
|
|
|
struct tag_entry *next, *entry;
|
|
|
|
|
|
|
|
|
|
while ((next = *tag_list) != NULL) {
|
|
|
|
|
int cmp = strcmp(next->tag->name, tag->name);
|
|
|
|
|
|
|
|
|
|
/* Already have it? */
|
|
|
|
|
if (!cmp)
|
|
|
|
|
return next->tag;
|
|
|
|
|
/* Is the entry larger? If so, insert here */
|
|
|
|
|
if (cmp > 0)
|
|
|
|
|
break;
|
|
|
|
|
/* Continue traversing the list */
|
|
|
|
|
tag_list = &next->next;
|
2013-11-02 01:12:42 +00:00
|
|
|
|
}
|
Get rid of crazy empty tag_list element at the start
So this is totally unrelated to the git repository format, except for
the fact that I noticed it while writing the git saving code.
The subsurface divetag list handling is being stupid, and has a
initial dummy entry at the head of the list for no good reason.
I say "no good reason", because there *is* a reason for it: it allows
code to avoid the special case of empty list and adding entries to
before the first entry etc etc. But that reason is a really *bad*
reason, because it's valid only because people don't understand basic
list manipulation and pointers to pointers.
So get rid of the dummy element, and do things right instead - by
passing a *pointer* to the list, instead of the list. And then when
traversing the list and looking for a place to insert things, don't go
to the next entry - just update the "pointer to pointer" to point to
the address of the next entry. Each entry in a C linked list is no
different than the list itself, so you can use the pointer to the
pointer to the next entry as a pointer to the list.
This is a pet peeve of mine. The real beauty of pointers can never be
understood unless you understand the indirection they allow. People
who grew up with Pascal and were corrupted by that mindset are
mentally stunted. Niklaus Wirth has a lot to answer for!
But never fear. You too can overcome that mental limitation, it just
needs some brain exercise. Reading this patch may help. In particular,
contemplate the new "taglist_add_divetag()".
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
|
|
|
|
|
|
|
|
|
/* Insert in front of it */
|
|
|
|
|
entry = malloc(sizeof(struct tag_entry));
|
|
|
|
|
entry->next = next;
|
|
|
|
|
entry->tag = tag;
|
|
|
|
|
*tag_list = entry;
|
|
|
|
|
return tag;
|
2013-11-02 01:12:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
Get rid of crazy empty tag_list element at the start
So this is totally unrelated to the git repository format, except for
the fact that I noticed it while writing the git saving code.
The subsurface divetag list handling is being stupid, and has a
initial dummy entry at the head of the list for no good reason.
I say "no good reason", because there *is* a reason for it: it allows
code to avoid the special case of empty list and adding entries to
before the first entry etc etc. But that reason is a really *bad*
reason, because it's valid only because people don't understand basic
list manipulation and pointers to pointers.
So get rid of the dummy element, and do things right instead - by
passing a *pointer* to the list, instead of the list. And then when
traversing the list and looking for a place to insert things, don't go
to the next entry - just update the "pointer to pointer" to point to
the address of the next entry. Each entry in a C linked list is no
different than the list itself, so you can use the pointer to the
pointer to the next entry as a pointer to the list.
This is a pet peeve of mine. The real beauty of pointers can never be
understood unless you understand the indirection they allow. People
who grew up with Pascal and were corrupted by that mindset are
mentally stunted. Niklaus Wirth has a lot to answer for!
But never fear. You too can overcome that mental limitation, it just
needs some brain exercise. Reading this patch may help. In particular,
contemplate the new "taglist_add_divetag()".
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
|
|
|
|
struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag)
|
2013-11-02 01:12:42 +00:00
|
|
|
|
{
|
2016-03-10 02:18:58 +00:00
|
|
|
|
size_t i = 0;
|
|
|
|
|
int is_default_tag = 0;
|
2013-11-02 01:12:42 +00:00
|
|
|
|
struct divetag *ret_tag, *new_tag;
|
|
|
|
|
const char *translation;
|
|
|
|
|
new_tag = malloc(sizeof(struct divetag));
|
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++) {
|
2013-11-19 15:12:34 +00:00
|
|
|
|
if (strcmp(default_tags[i], tag) == 0) {
|
|
|
|
|
is_default_tag = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Only translate default tags */
|
|
|
|
|
if (is_default_tag) {
|
|
|
|
|
translation = translate("gettextFromC", tag);
|
2014-02-28 04:09:57 +00:00
|
|
|
|
new_tag->name = malloc(strlen(translation) + 1);
|
|
|
|
|
memcpy(new_tag->name, translation, strlen(translation) + 1);
|
|
|
|
|
new_tag->source = malloc(strlen(tag) + 1);
|
|
|
|
|
memcpy(new_tag->source, tag, strlen(tag) + 1);
|
2013-11-19 15:12:34 +00:00
|
|
|
|
} else {
|
|
|
|
|
new_tag->source = NULL;
|
2014-02-28 04:09:57 +00:00
|
|
|
|
new_tag->name = malloc(strlen(tag) + 1);
|
|
|
|
|
memcpy(new_tag->name, tag, strlen(tag) + 1);
|
2013-11-02 01:12:42 +00:00
|
|
|
|
}
|
|
|
|
|
/* Try to insert new_tag into g_tag_list if we are not operating on it */
|
Get rid of crazy empty tag_list element at the start
So this is totally unrelated to the git repository format, except for
the fact that I noticed it while writing the git saving code.
The subsurface divetag list handling is being stupid, and has a
initial dummy entry at the head of the list for no good reason.
I say "no good reason", because there *is* a reason for it: it allows
code to avoid the special case of empty list and adding entries to
before the first entry etc etc. But that reason is a really *bad*
reason, because it's valid only because people don't understand basic
list manipulation and pointers to pointers.
So get rid of the dummy element, and do things right instead - by
passing a *pointer* to the list, instead of the list. And then when
traversing the list and looking for a place to insert things, don't go
to the next entry - just update the "pointer to pointer" to point to
the address of the next entry. Each entry in a C linked list is no
different than the list itself, so you can use the pointer to the
pointer to the next entry as a pointer to the list.
This is a pet peeve of mine. The real beauty of pointers can never be
understood unless you understand the indirection they allow. People
who grew up with Pascal and were corrupted by that mindset are
mentally stunted. Niklaus Wirth has a lot to answer for!
But never fear. You too can overcome that mental limitation, it just
needs some brain exercise. Reading this patch may help. In particular,
contemplate the new "taglist_add_divetag()".
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
|
|
|
|
if (tag_list != &g_tag_list) {
|
|
|
|
|
ret_tag = taglist_add_divetag(&g_tag_list, new_tag);
|
2013-11-02 01:12:42 +00:00
|
|
|
|
/* g_tag_list already contains new_tag, free the duplicate */
|
|
|
|
|
if (ret_tag != new_tag)
|
|
|
|
|
taglist_free_divetag(new_tag);
|
|
|
|
|
ret_tag = taglist_add_divetag(tag_list, ret_tag);
|
|
|
|
|
} else {
|
|
|
|
|
ret_tag = taglist_add_divetag(tag_list, new_tag);
|
|
|
|
|
if (ret_tag != new_tag)
|
|
|
|
|
taglist_free_divetag(new_tag);
|
|
|
|
|
}
|
|
|
|
|
return ret_tag;
|
|
|
|
|
}
|
|
|
|
|
|
Get rid of crazy empty tag_list element at the start
So this is totally unrelated to the git repository format, except for
the fact that I noticed it while writing the git saving code.
The subsurface divetag list handling is being stupid, and has a
initial dummy entry at the head of the list for no good reason.
I say "no good reason", because there *is* a reason for it: it allows
code to avoid the special case of empty list and adding entries to
before the first entry etc etc. But that reason is a really *bad*
reason, because it's valid only because people don't understand basic
list manipulation and pointers to pointers.
So get rid of the dummy element, and do things right instead - by
passing a *pointer* to the list, instead of the list. And then when
traversing the list and looking for a place to insert things, don't go
to the next entry - just update the "pointer to pointer" to point to
the address of the next entry. Each entry in a C linked list is no
different than the list itself, so you can use the pointer to the
pointer to the next entry as a pointer to the list.
This is a pet peeve of mine. The real beauty of pointers can never be
understood unless you understand the indirection they allow. People
who grew up with Pascal and were corrupted by that mindset are
mentally stunted. Niklaus Wirth has a lot to answer for!
But never fear. You too can overcome that mental limitation, it just
needs some brain exercise. Reading this patch may help. In particular,
contemplate the new "taglist_add_divetag()".
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
|
|
|
|
void taglist_free(struct tag_entry *entry)
|
|
|
|
|
{
|
2014-12-21 19:05:51 +00:00
|
|
|
|
STRUCTURED_LIST_FREE(struct tag_entry, entry, free)
|
2013-11-02 01:12:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Merge src1 and src2, write to *dst */
|
Get rid of crazy empty tag_list element at the start
So this is totally unrelated to the git repository format, except for
the fact that I noticed it while writing the git saving code.
The subsurface divetag list handling is being stupid, and has a
initial dummy entry at the head of the list for no good reason.
I say "no good reason", because there *is* a reason for it: it allows
code to avoid the special case of empty list and adding entries to
before the first entry etc etc. But that reason is a really *bad*
reason, because it's valid only because people don't understand basic
list manipulation and pointers to pointers.
So get rid of the dummy element, and do things right instead - by
passing a *pointer* to the list, instead of the list. And then when
traversing the list and looking for a place to insert things, don't go
to the next entry - just update the "pointer to pointer" to point to
the address of the next entry. Each entry in a C linked list is no
different than the list itself, so you can use the pointer to the
pointer to the next entry as a pointer to the list.
This is a pet peeve of mine. The real beauty of pointers can never be
understood unless you understand the indirection they allow. People
who grew up with Pascal and were corrupted by that mindset are
mentally stunted. Niklaus Wirth has a lot to answer for!
But never fear. You too can overcome that mental limitation, it just
needs some brain exercise. Reading this patch may help. In particular,
contemplate the new "taglist_add_divetag()".
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
|
|
|
|
static void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2)
|
2013-11-02 01:12:42 +00:00
|
|
|
|
{
|
Get rid of crazy empty tag_list element at the start
So this is totally unrelated to the git repository format, except for
the fact that I noticed it while writing the git saving code.
The subsurface divetag list handling is being stupid, and has a
initial dummy entry at the head of the list for no good reason.
I say "no good reason", because there *is* a reason for it: it allows
code to avoid the special case of empty list and adding entries to
before the first entry etc etc. But that reason is a really *bad*
reason, because it's valid only because people don't understand basic
list manipulation and pointers to pointers.
So get rid of the dummy element, and do things right instead - by
passing a *pointer* to the list, instead of the list. And then when
traversing the list and looking for a place to insert things, don't go
to the next entry - just update the "pointer to pointer" to point to
the address of the next entry. Each entry in a C linked list is no
different than the list itself, so you can use the pointer to the
pointer to the next entry as a pointer to the list.
This is a pet peeve of mine. The real beauty of pointers can never be
understood unless you understand the indirection they allow. People
who grew up with Pascal and were corrupted by that mindset are
mentally stunted. Niklaus Wirth has a lot to answer for!
But never fear. You too can overcome that mental limitation, it just
needs some brain exercise. Reading this patch may help. In particular,
contemplate the new "taglist_add_divetag()".
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
|
|
|
|
struct tag_entry *entry;
|
|
|
|
|
|
|
|
|
|
for (entry = src1; entry; entry = entry->next)
|
|
|
|
|
taglist_add_divetag(dst, entry->tag);
|
|
|
|
|
for (entry = src2; entry; entry = entry->next)
|
|
|
|
|
taglist_add_divetag(dst, entry->tag);
|
2013-11-02 01:12:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void taglist_init_global()
|
|
|
|
|
{
|
2016-03-10 02:18:58 +00:00
|
|
|
|
size_t i;
|
2013-11-02 01:12:42 +00:00
|
|
|
|
|
2014-02-28 04:09:57 +00:00
|
|
|
|
for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++)
|
Get rid of crazy empty tag_list element at the start
So this is totally unrelated to the git repository format, except for
the fact that I noticed it while writing the git saving code.
The subsurface divetag list handling is being stupid, and has a
initial dummy entry at the head of the list for no good reason.
I say "no good reason", because there *is* a reason for it: it allows
code to avoid the special case of empty list and adding entries to
before the first entry etc etc. But that reason is a really *bad*
reason, because it's valid only because people don't understand basic
list manipulation and pointers to pointers.
So get rid of the dummy element, and do things right instead - by
passing a *pointer* to the list, instead of the list. And then when
traversing the list and looking for a place to insert things, don't go
to the next entry - just update the "pointer to pointer" to point to
the address of the next entry. Each entry in a C linked list is no
different than the list itself, so you can use the pointer to the
pointer to the next entry as a pointer to the list.
This is a pet peeve of mine. The real beauty of pointers can never be
understood unless you understand the indirection they allow. People
who grew up with Pascal and were corrupted by that mindset are
mentally stunted. Niklaus Wirth has a lot to answer for!
But never fear. You too can overcome that mental limitation, it just
needs some brain exercise. Reading this patch may help. In particular,
contemplate the new "taglist_add_divetag()".
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
|
|
|
|
taglist_add_tag(&g_tag_list, default_tags[i]);
|
2013-11-02 01:12:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-11-11 10:20:13 +00:00
|
|
|
|
bool taglist_contains(struct tag_entry *tag_list, const char *tag)
|
|
|
|
|
{
|
|
|
|
|
while (tag_list) {
|
|
|
|
|
if (same_string(tag_list->tag->name, tag))
|
|
|
|
|
return true;
|
|
|
|
|
tag_list = tag_list->next;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-10 14:31:37 +00:00
|
|
|
|
// check if all tags in subtl are included in supertl (so subtl is a subset of supertl)
|
|
|
|
|
static bool taglist_contains_all(struct tag_entry *subtl, struct tag_entry *supertl)
|
|
|
|
|
{
|
|
|
|
|
while (subtl) {
|
|
|
|
|
if (!taglist_contains(supertl, subtl->tag->name))
|
|
|
|
|
return false;
|
|
|
|
|
subtl = subtl->next;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2015-05-02 19:16:03 +00:00
|
|
|
|
}
|
2015-02-10 14:31:37 +00:00
|
|
|
|
|
2015-05-02 19:16:03 +00:00
|
|
|
|
struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list)
|
|
|
|
|
{
|
|
|
|
|
struct tag_entry *added_list = NULL;
|
|
|
|
|
while (new_list) {
|
|
|
|
|
if (!taglist_contains(original_list, new_list->tag->name))
|
|
|
|
|
taglist_add_tag(&added_list, new_list->tag->name);
|
|
|
|
|
new_list = new_list->next;
|
|
|
|
|
}
|
|
|
|
|
return added_list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void dump_taglist(const char *intro, struct tag_entry *tl)
|
|
|
|
|
{
|
|
|
|
|
char *comma = "";
|
|
|
|
|
fprintf(stderr, "%s", intro);
|
|
|
|
|
while(tl) {
|
|
|
|
|
fprintf(stderr, "%s %s", comma, tl->tag->name);
|
|
|
|
|
comma = ",";
|
|
|
|
|
tl = tl->next;
|
|
|
|
|
}
|
|
|
|
|
fprintf(stderr, "\n");
|
2015-02-10 14:31:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if tl1 is both a subset and superset of tl2 they must be the same
|
|
|
|
|
bool taglist_equal(struct tag_entry *tl1, struct tag_entry *tl2)
|
|
|
|
|
{
|
|
|
|
|
return taglist_contains_all(tl1, tl2) && taglist_contains_all(tl2, tl1);
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-13 00:01:58 +00:00
|
|
|
|
// count the dives where the tag list contains the given tag
|
2014-11-11 10:20:13 +00:00
|
|
|
|
int count_dives_with_tag(const char *tag)
|
|
|
|
|
{
|
|
|
|
|
int i, counter = 0;
|
|
|
|
|
struct dive *d;
|
|
|
|
|
|
|
|
|
|
for_each_dive (i, d) {
|
2014-11-13 20:45:32 +00:00
|
|
|
|
if (same_string(tag, "")) {
|
|
|
|
|
// count dives with no tags
|
|
|
|
|
if (d->tag_list == NULL)
|
|
|
|
|
counter++;
|
|
|
|
|
} else if (taglist_contains(d->tag_list, tag)) {
|
2014-11-11 10:20:13 +00:00
|
|
|
|
counter++;
|
2014-11-13 20:45:32 +00:00
|
|
|
|
}
|
2014-11-11 10:20:13 +00:00
|
|
|
|
}
|
|
|
|
|
return counter;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-13 00:01:58 +00:00
|
|
|
|
extern bool string_sequence_contains(const char *string_sequence, const char *text);
|
|
|
|
|
|
|
|
|
|
// count the dives where the person is included in the comma separated string sequences of buddies or divemasters
|
|
|
|
|
int count_dives_with_person(const char *person)
|
|
|
|
|
{
|
|
|
|
|
int i, counter = 0;
|
|
|
|
|
struct dive *d;
|
|
|
|
|
|
|
|
|
|
for_each_dive (i, d) {
|
2014-11-13 20:45:32 +00:00
|
|
|
|
if (same_string(person, "")) {
|
|
|
|
|
// solo dive
|
|
|
|
|
if (same_string(d->buddy, "") && same_string(d->divemaster, ""))
|
|
|
|
|
counter++;
|
|
|
|
|
} else if (string_sequence_contains(d->buddy, person) || string_sequence_contains(d->divemaster, person)) {
|
2014-11-13 00:01:58 +00:00
|
|
|
|
counter++;
|
2014-11-13 20:45:32 +00:00
|
|
|
|
}
|
2014-11-13 00:01:58 +00:00
|
|
|
|
}
|
|
|
|
|
return counter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// count the dives with exactly the location
|
|
|
|
|
int count_dives_with_location(const char *location)
|
|
|
|
|
{
|
|
|
|
|
int i, counter = 0;
|
|
|
|
|
struct dive *d;
|
|
|
|
|
|
|
|
|
|
for_each_dive (i, d) {
|
2015-02-13 02:52:54 +00:00
|
|
|
|
if (same_string(get_dive_location(d), location))
|
2014-11-13 00:01:58 +00:00
|
|
|
|
counter++;
|
|
|
|
|
}
|
|
|
|
|
return counter;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-13 20:35:12 +00:00
|
|
|
|
// count the dives with exactly the suit
|
|
|
|
|
int count_dives_with_suit(const char *suit)
|
|
|
|
|
{
|
|
|
|
|
int i, counter = 0;
|
|
|
|
|
struct dive *d;
|
|
|
|
|
|
|
|
|
|
for_each_dive (i, d) {
|
|
|
|
|
if (same_string(d->suit, suit))
|
|
|
|
|
counter++;
|
|
|
|
|
}
|
|
|
|
|
return counter;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-22 19:32:27 +00:00
|
|
|
|
/*
|
|
|
|
|
* Merging two dives can be subtle, because there's two different ways
|
|
|
|
|
* of merging:
|
|
|
|
|
*
|
|
|
|
|
* (a) two distinctly _different_ dives that have the same dive computer
|
|
|
|
|
* are merged into one longer dive, because the user asked for it
|
|
|
|
|
* in the divelist.
|
|
|
|
|
*
|
2015-11-15 18:15:40 +00:00
|
|
|
|
* Because this case is with the same dive computer, we *know* the
|
2015-09-22 19:32:27 +00:00
|
|
|
|
* two must have a different start time, and "offset" is the relative
|
|
|
|
|
* time difference between the two.
|
|
|
|
|
*
|
2015-11-15 18:15:40 +00:00
|
|
|
|
* (a) two different dive computers that we might want to merge into
|
2015-09-22 19:32:27 +00:00
|
|
|
|
* one single dive with multiple dive computers.
|
|
|
|
|
*
|
|
|
|
|
* This is the "try_to_merge()" case, which will have offset == 0,
|
2015-11-16 09:42:36 +00:00
|
|
|
|
* even if the dive times might be different.
|
2015-09-22 19:32:27 +00:00
|
|
|
|
*/
|
2013-10-05 07:29:09 +00:00
|
|
|
|
struct dive *merge_dives(struct dive *a, struct dive *b, int offset, bool prefer_downloaded)
|
2012-11-11 06:20:05 +00:00
|
|
|
|
{
|
|
|
|
|
struct dive *res = alloc_dive();
|
Add special download modes to force updates from the divecomputer
This will hopefully not be something we need often, but if we improve
support for a divecomputer (either in libdivecomputer or in our native
Uemis code or even in the way we handle (and potentially discard) events),
then it is extremely useful to be able to say "re-download things
from the divecomputer and for things that were not edited in Subsurface,
don't try to merge the data (which gives BAD results if for example you
fixed a bug in the depth calculation in libdivecomputer) but instead
simply take the samples, the events and some of the other unedited data
straight from the download".
This commit implements just that - a "force download" checkbox in the
download dialog that makes us reimport all dives from the dive computer,
even the ones we already have, and an "always prefer downloaded dive"
checkbox that then tells Subsurface not to merge but simply to take the
data from the downloaded dive - without overwriting the things we have
already edited in Subsurface (like location, buddy, equipment, etc).
This, as a precaution, refuses to merge dives that don't have identical
start times. So if you have edited the date / time of a dive or if you
have previously merged your dive with a different dive computer (and
therefore modified samples and events) you are out of luck.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-11 13:29:26 +00:00
|
|
|
|
struct dive *dl = NULL;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
2015-09-22 19:32:27 +00:00
|
|
|
|
if (offset) {
|
|
|
|
|
/*
|
|
|
|
|
* If "likely_same_dive()" returns true, that means that
|
|
|
|
|
* it is *not* the same dive computer, and we do not want
|
|
|
|
|
* to try to turn it into a single longer dive. So we'd
|
|
|
|
|
* join them as two separate dive computers at zero offset.
|
|
|
|
|
*/
|
|
|
|
|
if (likely_same_dive(a, b))
|
|
|
|
|
offset = 0;
|
|
|
|
|
} else {
|
|
|
|
|
/* Aim for newly downloaded dives to be 'b' (keep old dive data first) */
|
|
|
|
|
if (a->downloaded && !b->downloaded) {
|
|
|
|
|
struct dive *tmp = a;
|
|
|
|
|
a = b;
|
|
|
|
|
b = tmp;
|
|
|
|
|
}
|
|
|
|
|
if (prefer_downloaded && b->downloaded)
|
|
|
|
|
dl = b;
|
2012-11-21 23:34:04 +00:00
|
|
|
|
}
|
2014-01-11 07:46:05 +00:00
|
|
|
|
|
2017-02-16 01:04:25 +00:00
|
|
|
|
if (same_string(a->dc.model, "planned dive")) {
|
2017-02-02 14:31:52 +00:00
|
|
|
|
struct dive *tmp = a;
|
|
|
|
|
a = b;
|
|
|
|
|
b = tmp;
|
|
|
|
|
}
|
2012-11-25 04:29:14 +00:00
|
|
|
|
res->when = dl ? dl->when : a->when;
|
2012-11-11 10:00:27 +00:00
|
|
|
|
res->selected = a->selected || b->selected;
|
2012-11-11 06:20:05 +00:00
|
|
|
|
merge_trip(res, a, b);
|
2011-09-23 01:01:01 +00:00
|
|
|
|
MERGE_TXT(res, a, b, notes);
|
|
|
|
|
MERGE_TXT(res, a, b, buddy);
|
|
|
|
|
MERGE_TXT(res, a, b, divemaster);
|
2011-12-07 19:58:16 +00:00
|
|
|
|
MERGE_MAX(res, a, b, rating);
|
2012-08-14 23:07:25 +00:00
|
|
|
|
MERGE_TXT(res, a, b, suit);
|
Be smarter about dive renumbering when merging dives
We really have two different cases for merging dives:
(a) downloading a new dive from a dive computer, and merging it with an
existing dive that we had already created using a different dive
computer. This is the "try_to_merge()" case, called from
"process_dives()
(b) merging two different dives into one longer dive. This is the
"merge_two_dives()" case when you explicitly merge dives using the
divelist.
While a lot of the issues are the same, many details differ, and one of
the details is how dive numbering should be handled.
In particular, when you download from a dive computer and merge with an
existing dive, you want too take the *maximum* dive number, because the
dive computer notion of which dive it is may well not match what the
user dive number is.
On the other hand, when you explicitly merge in the dive list, you end
up renumbering not just the dive you are merging, but also all
subsequent dives, since you now have one fewer dives overall. So that
case already has to be handled by the caller.
Now, the simpler "download from dive computer" case was broken by commit
ce3a78efcac2 ("Assign lower number to a merged dive instead of higher
one"). It fixed the numbering for the divelist case, but broke the
download case.
So this commit reverts commit ce3a78efcac2, and instead extends and
clarifies the dive renumbering that "merge_two_dives()" already did. It
now explicitly renumbers not just the following dives, but also
renumbers the merged dive itself, so now we can go back to the old "take
the bigger dive number" for the core merging, which fixes the download
case.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-03-30 19:42:27 +00:00
|
|
|
|
MERGE_MAX(res, a, b, number);
|
2012-12-11 20:30:34 +00:00
|
|
|
|
MERGE_NONZERO(res, a, b, cns);
|
2012-11-10 18:18:10 +00:00
|
|
|
|
MERGE_NONZERO(res, a, b, visibility);
|
2015-01-30 20:40:48 +00:00
|
|
|
|
MERGE_NONZERO(res, a, b, picture_list);
|
Get rid of crazy empty tag_list element at the start
So this is totally unrelated to the git repository format, except for
the fact that I noticed it while writing the git saving code.
The subsurface divetag list handling is being stupid, and has a
initial dummy entry at the head of the list for no good reason.
I say "no good reason", because there *is* a reason for it: it allows
code to avoid the special case of empty list and adding entries to
before the first entry etc etc. But that reason is a really *bad*
reason, because it's valid only because people don't understand basic
list manipulation and pointers to pointers.
So get rid of the dummy element, and do things right instead - by
passing a *pointer* to the list, instead of the list. And then when
traversing the list and looking for a place to insert things, don't go
to the next entry - just update the "pointer to pointer" to point to
the address of the next entry. Each entry in a C linked list is no
different than the list itself, so you can use the pointer to the
pointer to the next entry as a pointer to the list.
This is a pet peeve of mine. The real beauty of pointers can never be
understood unless you understand the indirection they allow. People
who grew up with Pascal and were corrupted by that mindset are
mentally stunted. Niklaus Wirth has a lot to answer for!
But never fear. You too can overcome that mental limitation, it just
needs some brain exercise. Reading this patch may help. In particular,
contemplate the new "taglist_add_divetag()".
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
|
|
|
|
taglist_merge(&res->tag_list, a->tag_list, b->tag_list);
|
2012-10-29 18:27:14 +00:00
|
|
|
|
merge_equipment(res, a, b);
|
2017-02-19 20:56:54 +00:00
|
|
|
|
merge_temperatures(res, a, b);
|
Add special download modes to force updates from the divecomputer
This will hopefully not be something we need often, but if we improve
support for a divecomputer (either in libdivecomputer or in our native
Uemis code or even in the way we handle (and potentially discard) events),
then it is extremely useful to be able to say "re-download things
from the divecomputer and for things that were not edited in Subsurface,
don't try to merge the data (which gives BAD results if for example you
fixed a bug in the depth calculation in libdivecomputer) but instead
simply take the samples, the events and some of the other unedited data
straight from the download".
This commit implements just that - a "force download" checkbox in the
download dialog that makes us reimport all dives from the dive computer,
even the ones we already have, and an "always prefer downloaded dive"
checkbox that then tells Subsurface not to merge but simply to take the
data from the downloaded dive - without overwriting the things we have
already edited in Subsurface (like location, buddy, equipment, etc).
This, as a precaution, refuses to merge dives that don't have identical
start times. So if you have edited the date / time of a dive or if you
have previously merged your dive with a different dive computer (and
therefore modified samples and events) you are out of luck.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-11 13:29:26 +00:00
|
|
|
|
if (dl) {
|
2013-01-23 00:57:07 +00:00
|
|
|
|
/* If we prefer downloaded, do those first, and get rid of "might be same" computers */
|
|
|
|
|
join_dive_computers(&res->dc, &dl->dc, &a->dc, 1);
|
2014-05-14 04:08:06 +00:00
|
|
|
|
} else if (offset && might_be_same_device(&a->dc, &b->dc))
|
2012-11-25 20:39:08 +00:00
|
|
|
|
interleave_dive_computers(&res->dc, &a->dc, &b->dc, offset);
|
|
|
|
|
else
|
2013-01-23 00:57:07 +00:00
|
|
|
|
join_dive_computers(&res->dc, &a->dc, &b->dc, 0);
|
2017-02-22 02:18:44 +00:00
|
|
|
|
/* we take the first dive site, unless it's empty */
|
|
|
|
|
if (a->dive_site_uuid && !dive_site_is_empty(get_dive_site_by_uuid(a->dive_site_uuid)))
|
|
|
|
|
res->dive_site_uuid = a->dive_site_uuid;
|
|
|
|
|
else
|
|
|
|
|
res->dive_site_uuid = b->dive_site_uuid;
|
2012-11-24 02:51:27 +00:00
|
|
|
|
fixup_dive(res);
|
|
|
|
|
return res;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
}
|
2013-01-31 03:09:16 +00:00
|
|
|
|
|
2015-10-02 01:17:37 +00:00
|
|
|
|
// copy_dive(), but retaining the new ID for the copied dive
|
|
|
|
|
static struct dive *create_new_copy(struct dive *from)
|
|
|
|
|
{
|
|
|
|
|
struct dive *to = alloc_dive();
|
|
|
|
|
int id;
|
|
|
|
|
|
|
|
|
|
// alloc_dive() gave us a new ID, we just need to
|
|
|
|
|
// make sure it's not overwritten.
|
|
|
|
|
id = to->id;
|
|
|
|
|
copy_dive(from, to);
|
|
|
|
|
to->id = id;
|
|
|
|
|
return to;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-06 15:41:36 +00:00
|
|
|
|
static void force_fixup_dive(struct dive *d)
|
|
|
|
|
{
|
|
|
|
|
struct divecomputer *dc = &d->dc;
|
|
|
|
|
int old_temp = dc->watertemp.mkelvin;
|
|
|
|
|
int old_mintemp = d->mintemp.mkelvin;
|
|
|
|
|
int old_maxtemp = d->maxtemp.mkelvin;
|
|
|
|
|
duration_t old_duration = d->duration;
|
|
|
|
|
|
|
|
|
|
d->maxdepth.mm = 0;
|
|
|
|
|
dc->maxdepth.mm = 0;
|
|
|
|
|
d->watertemp.mkelvin = 0;
|
|
|
|
|
dc->watertemp.mkelvin = 0;
|
|
|
|
|
d->duration.seconds = 0;
|
|
|
|
|
d->maxtemp.mkelvin = 0;
|
|
|
|
|
d->mintemp.mkelvin = 0;
|
|
|
|
|
|
|
|
|
|
fixup_dive(d);
|
|
|
|
|
|
|
|
|
|
if (!d->watertemp.mkelvin)
|
|
|
|
|
d->watertemp.mkelvin = old_temp;
|
|
|
|
|
|
|
|
|
|
if (!dc->watertemp.mkelvin)
|
|
|
|
|
dc->watertemp.mkelvin = old_temp;
|
|
|
|
|
|
|
|
|
|
if (!d->maxtemp.mkelvin)
|
|
|
|
|
d->maxtemp.mkelvin = old_maxtemp;
|
|
|
|
|
|
|
|
|
|
if (!d->mintemp.mkelvin)
|
|
|
|
|
d->mintemp.mkelvin = old_mintemp;
|
|
|
|
|
|
|
|
|
|
if (!d->duration.seconds)
|
|
|
|
|
d->duration = old_duration;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-02 01:17:37 +00:00
|
|
|
|
/*
|
|
|
|
|
* Split a dive that has a surface interval from samples 'a' to 'b'
|
|
|
|
|
* into two dives.
|
|
|
|
|
*/
|
|
|
|
|
static int split_dive_at(struct dive *dive, int a, int b)
|
|
|
|
|
{
|
2016-03-10 02:18:58 +00:00
|
|
|
|
int i, nr;
|
|
|
|
|
uint32_t t;
|
2015-10-02 01:17:37 +00:00
|
|
|
|
struct dive *d1, *d2;
|
|
|
|
|
struct divecomputer *dc1, *dc2;
|
|
|
|
|
struct event *event, **evp;
|
|
|
|
|
|
2015-10-03 13:44:16 +00:00
|
|
|
|
/* if we can't find the dive in the dive list, don't bother */
|
2015-10-04 11:02:54 +00:00
|
|
|
|
if ((nr = get_divenr(dive)) < 0)
|
2015-10-03 13:44:16 +00:00
|
|
|
|
return 0;
|
|
|
|
|
|
2015-10-02 01:17:37 +00:00
|
|
|
|
/* We're not trying to be efficient here.. */
|
|
|
|
|
d1 = create_new_copy(dive);
|
|
|
|
|
d2 = create_new_copy(dive);
|
|
|
|
|
|
2015-10-03 11:25:52 +00:00
|
|
|
|
/* now unselect the first first segment so we don't keep all
|
|
|
|
|
* dives selected by mistake. But do keep the second one selected
|
|
|
|
|
* so the algorithm keeps splitting the dive further */
|
|
|
|
|
d1->selected = false;
|
|
|
|
|
|
2015-10-02 01:17:37 +00:00
|
|
|
|
dc1 = &d1->dc;
|
|
|
|
|
dc2 = &d2->dc;
|
|
|
|
|
/*
|
|
|
|
|
* Cut off the samples of d1 at the beginning
|
|
|
|
|
* of the interval.
|
|
|
|
|
*/
|
|
|
|
|
dc1->samples = a;
|
|
|
|
|
|
|
|
|
|
/* And get rid of the 'b' first samples of d2 */
|
|
|
|
|
dc2->samples -= b;
|
|
|
|
|
memmove(dc2->sample, dc2->sample+b, dc2->samples * sizeof(struct sample));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* This is where we cut off events from d1,
|
|
|
|
|
* and shift everything in d2
|
|
|
|
|
*/
|
|
|
|
|
t = dc2->sample[0].time.seconds;
|
|
|
|
|
d2->when += t;
|
|
|
|
|
for (i = 0; i < dc2->samples; i++)
|
|
|
|
|
dc2->sample[i].time.seconds -= t;
|
|
|
|
|
|
|
|
|
|
/* Remove the events past 't' from d1 */
|
|
|
|
|
evp = &dc1->events;
|
|
|
|
|
while ((event = *evp) != NULL && event->time.seconds < t)
|
|
|
|
|
evp = &event->next;
|
|
|
|
|
*evp = NULL;
|
|
|
|
|
while (event) {
|
|
|
|
|
struct event *next = event->next;
|
|
|
|
|
free(event);
|
|
|
|
|
event = next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Remove the events before 't' from d2, and shift the rest */
|
|
|
|
|
evp = &dc2->events;
|
|
|
|
|
while ((event = *evp) != NULL) {
|
|
|
|
|
if (event->time.seconds < t) {
|
|
|
|
|
*evp = event->next;
|
|
|
|
|
free(event);
|
|
|
|
|
} else {
|
|
|
|
|
event->time.seconds -= t;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-06 15:41:36 +00:00
|
|
|
|
|
|
|
|
|
force_fixup_dive(d1);
|
|
|
|
|
force_fixup_dive(d2);
|
|
|
|
|
|
2015-10-03 11:11:11 +00:00
|
|
|
|
if (dive->divetrip) {
|
|
|
|
|
d1->divetrip = d2->divetrip = 0;
|
|
|
|
|
add_dive_to_trip(d1, dive->divetrip);
|
|
|
|
|
add_dive_to_trip(d2, dive->divetrip);
|
|
|
|
|
}
|
2015-10-02 01:17:37 +00:00
|
|
|
|
|
2015-10-04 11:02:54 +00:00
|
|
|
|
delete_single_dive(nr);
|
|
|
|
|
add_single_dive(nr, d1);
|
2015-10-02 01:17:37 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Was the dive numbered? If it was the last dive, then we'll
|
|
|
|
|
* increment the dive number for the tail part that we split off.
|
|
|
|
|
* Otherwise the tail is unnumbered.
|
|
|
|
|
*/
|
|
|
|
|
if (d2->number) {
|
2015-10-04 11:02:54 +00:00
|
|
|
|
if (dive_table.nr == nr + 1)
|
2015-10-02 01:17:37 +00:00
|
|
|
|
d2->number++;
|
|
|
|
|
else
|
|
|
|
|
d2->number = 0;
|
|
|
|
|
}
|
2015-10-04 11:02:54 +00:00
|
|
|
|
add_single_dive(nr + 1, d2);
|
2015-10-02 01:17:37 +00:00
|
|
|
|
|
|
|
|
|
mark_divelist_changed(true);
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-04 11:13:10 +00:00
|
|
|
|
/* in freedive mode we split for as little as 10 seconds on the surface,
|
|
|
|
|
* otherwise we use a minute */
|
|
|
|
|
static bool should_split(struct divecomputer *dc, int t1, int t2)
|
|
|
|
|
{
|
|
|
|
|
int threshold = dc->divemode == FREEDIVE ? 10 : 60;
|
|
|
|
|
|
|
|
|
|
return t2 - t1 >= threshold;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-02 01:17:37 +00:00
|
|
|
|
/*
|
|
|
|
|
* Try to split a dive into multiple dives at a surface interval point.
|
|
|
|
|
*
|
|
|
|
|
* NOTE! We will not split dives with multiple dive computers, and
|
|
|
|
|
* only split when there is at least one surface event that has
|
|
|
|
|
* non-surface events on both sides.
|
|
|
|
|
*
|
|
|
|
|
* In other words, this is a (simplified) reversal of the dive merging.
|
|
|
|
|
*/
|
|
|
|
|
int split_dive(struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
int at_surface, surface_start;
|
|
|
|
|
struct divecomputer *dc;
|
|
|
|
|
|
|
|
|
|
if (!dive || (dc = &dive->dc)->next)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
surface_start = 0;
|
|
|
|
|
at_surface = 1;
|
|
|
|
|
for (i = 1; i < dc->samples; i++) {
|
|
|
|
|
struct sample *sample = dc->sample+i;
|
|
|
|
|
int surface_sample = sample->depth.mm < SURFACE_THRESHOLD;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We care about the transition from and to depth 0,
|
|
|
|
|
* not about the depth staying similar.
|
|
|
|
|
*/
|
|
|
|
|
if (at_surface == surface_sample)
|
|
|
|
|
continue;
|
|
|
|
|
at_surface = surface_sample;
|
|
|
|
|
|
|
|
|
|
// Did it become surface after having been non-surface? We found the start
|
|
|
|
|
if (at_surface) {
|
|
|
|
|
surface_start = i;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-15 18:15:40 +00:00
|
|
|
|
// Going down again? We want at least a minute from
|
2015-10-02 01:17:37 +00:00
|
|
|
|
// the surface start.
|
|
|
|
|
if (!surface_start)
|
|
|
|
|
continue;
|
2015-10-04 11:13:10 +00:00
|
|
|
|
if (!should_split(dc, dc->sample[surface_start].time.seconds, sample[i - 1].time.seconds))
|
2015-10-02 01:17:37 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
return split_dive_at(dive, surface_start, i-1);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-22 19:32:27 +00:00
|
|
|
|
/*
|
|
|
|
|
* "dc_maxtime()" is how much total time this dive computer
|
|
|
|
|
* has for this dive. Note that it can differ from "duration"
|
|
|
|
|
* if there are surface events in the middle.
|
|
|
|
|
*
|
|
|
|
|
* Still, we do ignore all but the last surface samples from the
|
|
|
|
|
* end, because some divecomputers just generate lots of them.
|
|
|
|
|
*/
|
|
|
|
|
static inline int dc_totaltime(const struct divecomputer *dc)
|
|
|
|
|
{
|
|
|
|
|
int time = dc->duration.seconds;
|
|
|
|
|
int nr = dc->samples;
|
|
|
|
|
|
|
|
|
|
while (nr--) {
|
|
|
|
|
struct sample *s = dc->sample + nr;
|
|
|
|
|
time = s->time.seconds;
|
|
|
|
|
if (s->depth.mm >= SURFACE_THRESHOLD)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return time;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The end of a dive is actually not trivial, because "duration"
|
|
|
|
|
* is not the duration until the end, but the time we spend under
|
|
|
|
|
* water, which can be very different if there are surface events
|
|
|
|
|
* during the dive.
|
|
|
|
|
*
|
|
|
|
|
* So walk the dive computers, looking for the longest actual
|
|
|
|
|
* time in the samples (and just default to the dive duration if
|
|
|
|
|
* there are no samples).
|
|
|
|
|
*/
|
|
|
|
|
static inline int dive_totaltime(const struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
int time = dive->duration.seconds;
|
|
|
|
|
const struct divecomputer *dc;
|
|
|
|
|
|
|
|
|
|
for_each_dc(dive, dc) {
|
|
|
|
|
int dc_time = dc_totaltime(dc);
|
|
|
|
|
if (dc_time > time)
|
|
|
|
|
time = dc_time;
|
|
|
|
|
}
|
|
|
|
|
return time;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timestamp_t dive_endtime(const struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
return dive->when + dive_totaltime(dive);
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-31 03:09:16 +00:00
|
|
|
|
struct dive *find_dive_including(timestamp_t when)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
struct dive *dive;
|
|
|
|
|
|
|
|
|
|
/* binary search, anyone? Too lazy for now;
|
|
|
|
|
* also we always use the duration from the first divecomputer
|
|
|
|
|
* could this ever be a problem? */
|
2014-10-11 11:25:52 +00:00
|
|
|
|
for_each_dive (i, dive) {
|
2015-09-22 19:32:27 +00:00
|
|
|
|
if (dive->when <= when && when <= dive_endtime(dive))
|
2013-01-31 03:09:16 +00:00
|
|
|
|
return dive;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-25 05:38:44 +00:00
|
|
|
|
bool time_during_dive_with_offset(struct dive *dive, timestamp_t when, timestamp_t offset)
|
|
|
|
|
{
|
2015-09-22 19:32:27 +00:00
|
|
|
|
timestamp_t start = dive->when;
|
|
|
|
|
timestamp_t end = dive_endtime(dive);
|
|
|
|
|
return start - offset <= when && when <= end + offset;
|
2015-06-25 05:38:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-10-05 07:29:09 +00:00
|
|
|
|
bool dive_within_time_range(struct dive *dive, timestamp_t when, timestamp_t offset)
|
2013-01-31 03:09:16 +00:00
|
|
|
|
{
|
2015-09-22 19:32:27 +00:00
|
|
|
|
timestamp_t start = dive->when;
|
|
|
|
|
timestamp_t end = dive_endtime(dive);
|
|
|
|
|
return when - offset <= start && end <= when + offset;
|
2013-01-31 03:09:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* find the n-th dive that is part of a group of dives within the offset around 'when'.
|
|
|
|
|
* How is that for a vague definition of what this function should do... */
|
|
|
|
|
struct dive *find_dive_n_near(timestamp_t when, int n, timestamp_t offset)
|
|
|
|
|
{
|
|
|
|
|
int i, j = 0;
|
|
|
|
|
struct dive *dive;
|
|
|
|
|
|
2014-10-11 11:25:52 +00:00
|
|
|
|
for_each_dive (i, dive) {
|
2013-01-31 03:09:16 +00:00
|
|
|
|
if (dive_within_time_range(dive, when, offset))
|
|
|
|
|
if (++j == n)
|
|
|
|
|
return dive;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2013-11-18 13:53:05 +00:00
|
|
|
|
|
|
|
|
|
void shift_times(const timestamp_t amount)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
struct dive *dive;
|
|
|
|
|
|
2014-10-11 11:25:52 +00:00
|
|
|
|
for_each_dive (i, dive) {
|
2013-11-18 13:53:05 +00:00
|
|
|
|
if (!dive->selected)
|
|
|
|
|
continue;
|
|
|
|
|
dive->when += amount;
|
2016-04-03 22:31:59 +00:00
|
|
|
|
invalidate_dive_cache(dive);
|
2013-11-18 13:53:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-03-20 20:57:49 +00:00
|
|
|
|
|
|
|
|
|
timestamp_t get_times()
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
struct dive *dive;
|
|
|
|
|
|
2014-10-11 11:25:52 +00:00
|
|
|
|
for_each_dive (i, dive) {
|
2014-03-20 20:57:49 +00:00
|
|
|
|
if (dive->selected)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return dive->when;
|
|
|
|
|
}
|
2014-04-17 14:34:21 +00:00
|
|
|
|
|
|
|
|
|
void set_userid(char *rUserId)
|
|
|
|
|
{
|
2014-12-18 18:47:00 +00:00
|
|
|
|
if (prefs.userid)
|
|
|
|
|
free(prefs.userid);
|
2014-10-11 21:35:31 +00:00
|
|
|
|
prefs.userid = strdup(rUserId);
|
2014-11-20 22:30:44 +00:00
|
|
|
|
if (strlen(prefs.userid) > 30)
|
|
|
|
|
prefs.userid[30]='\0';
|
2014-04-17 14:34:21 +00:00
|
|
|
|
}
|
2014-04-26 15:29:40 +00:00
|
|
|
|
|
2015-06-17 03:28:42 +00:00
|
|
|
|
/* this sets a usually unused copy of the preferences with the units
|
|
|
|
|
* that were active the last time the dive list was saved to git storage
|
|
|
|
|
* (this isn't used in XML files); storing the unit preferences in the
|
|
|
|
|
* data file is usually pointless (that's a setting of the software,
|
|
|
|
|
* not a property of the data), but it's a great hint of what the user
|
|
|
|
|
* might expect to see when creating a backend service that visualizes
|
|
|
|
|
* the dive list without Subsurface running - so this is basically a
|
|
|
|
|
* functionality for the core library that Subsurface itself doesn't
|
|
|
|
|
* use but that another consumer of the library (like an HTML exporter)
|
|
|
|
|
* will need */
|
|
|
|
|
void set_informational_units(char *units)
|
|
|
|
|
{
|
|
|
|
|
if (strstr(units, "METRIC")) {
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.unit_system = METRIC;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
} else if (strstr(units, "IMPERIAL")) {
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.unit_system = IMPERIAL;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
} else if (strstr(units, "PERSONALIZE")) {
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.unit_system = PERSONALIZE;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "METERS"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.length = METERS;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "FEET"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.length = FEET;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "LITER"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.volume = LITER;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "CUFT"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.volume = CUFT;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "BAR"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.pressure = BAR;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "PSI"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.pressure = PSI;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "PASCAL"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.pressure = PASCAL;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "CELSIUS"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.temperature = CELSIUS;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "FAHRENHEIT"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.temperature = FAHRENHEIT;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "KG"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.weight = KG;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "LBS"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.weight = LBS;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "SECONDS"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.vertical_speed_time = SECONDS;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
if (strstr(units, "MINUTES"))
|
2017-02-04 16:55:25 +00:00
|
|
|
|
git_prefs.units.vertical_speed_time = MINUTES;
|
2015-06-17 03:28:42 +00:00
|
|
|
|
}
|
2017-02-04 16:55:25 +00:00
|
|
|
|
|
2015-06-17 03:28:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-04 09:13:58 +00:00
|
|
|
|
void set_git_prefs(char *prefs)
|
|
|
|
|
{
|
|
|
|
|
if (strstr(prefs, "TANKBAR"))
|
|
|
|
|
git_prefs.tankbar = 1;
|
|
|
|
|
if (strstr(prefs, "DCCEILING"))
|
|
|
|
|
git_prefs.dcceiling = 1;
|
2017-02-04 16:55:25 +00:00
|
|
|
|
if (strstr(prefs, "SHOW_SETPOINT"))
|
|
|
|
|
git_prefs.show_ccr_setpoint = 1;
|
|
|
|
|
if (strstr(prefs, "SHOW_SENSORS"))
|
|
|
|
|
git_prefs.show_ccr_sensors = 1;
|
|
|
|
|
if (strstr(prefs, "PO2_GRAPH"))
|
|
|
|
|
git_prefs.pp_graphs.po2 = 1;
|
2017-02-04 09:13:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-23 16:53:44 +00:00
|
|
|
|
void average_max_depth(struct diveplan *dive, int *avg_depth, int *max_depth)
|
2014-04-26 15:29:40 +00:00
|
|
|
|
{
|
|
|
|
|
int integral = 0;
|
|
|
|
|
int last_time = 0;
|
|
|
|
|
int last_depth = 0;
|
|
|
|
|
struct divedatapoint *dp = dive->dp;
|
|
|
|
|
|
2015-04-02 08:49:24 +00:00
|
|
|
|
*max_depth = 0;
|
2014-07-10 20:11:44 +00:00
|
|
|
|
|
2014-04-26 15:29:40 +00:00
|
|
|
|
while (dp) {
|
|
|
|
|
if (dp->time) {
|
|
|
|
|
/* Ignore gas indication samples */
|
2017-03-10 12:37:54 +00:00
|
|
|
|
integral += (dp->depth.mm + last_depth) * (dp->time - last_time) / 2;
|
2014-04-26 15:29:40 +00:00
|
|
|
|
last_time = dp->time;
|
2017-03-10 12:37:54 +00:00
|
|
|
|
last_depth = dp->depth.mm;
|
|
|
|
|
if (dp->depth.mm > *max_depth)
|
|
|
|
|
*max_depth = dp->depth.mm;
|
2014-04-26 15:29:40 +00:00
|
|
|
|
}
|
|
|
|
|
dp = dp->next;
|
|
|
|
|
}
|
2014-07-15 10:14:15 +00:00
|
|
|
|
if (last_time)
|
2015-04-02 08:49:24 +00:00
|
|
|
|
*avg_depth = integral / last_time;
|
2014-07-15 10:14:15 +00:00
|
|
|
|
else
|
2015-04-02 08:49:24 +00:00
|
|
|
|
*avg_depth = *max_depth = 0;
|
2014-04-26 15:29:40 +00:00
|
|
|
|
}
|
2014-06-02 19:56:02 +00:00
|
|
|
|
|
2014-06-02 21:28:02 +00:00
|
|
|
|
struct picture *alloc_picture()
|
2014-06-02 19:56:02 +00:00
|
|
|
|
{
|
2014-06-02 21:28:02 +00:00
|
|
|
|
struct picture *pic = malloc(sizeof(struct picture));
|
|
|
|
|
if (!pic)
|
|
|
|
|
exit(1);
|
|
|
|
|
memset(pic, 0, sizeof(struct picture));
|
|
|
|
|
return pic;
|
2014-06-02 19:56:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-21 09:00:16 +00:00
|
|
|
|
static bool new_picture_for_dive(struct dive *d, const char *filename)
|
2014-06-08 18:55:03 +00:00
|
|
|
|
{
|
2014-10-11 11:25:52 +00:00
|
|
|
|
FOR_EACH_PICTURE (d) {
|
2014-06-08 18:55:03 +00:00
|
|
|
|
if (same_string(picture->filename, filename))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-01 16:29:19 +00:00
|
|
|
|
// only add pictures that have timestamps between 30 minutes before the dive and
|
|
|
|
|
// 30 minutes after the dive ends
|
|
|
|
|
#define D30MIN (30 * 60)
|
2015-04-24 15:10:55 +00:00
|
|
|
|
bool dive_check_picture_time(struct dive *d, int shift_time, timestamp_t timestamp)
|
2015-03-14 14:35:47 +00:00
|
|
|
|
{
|
|
|
|
|
offset_t offset;
|
|
|
|
|
if (timestamp) {
|
|
|
|
|
offset.seconds = timestamp - d->when + shift_time;
|
2015-09-22 19:32:27 +00:00
|
|
|
|
if (offset.seconds > -D30MIN && offset.seconds < dive_totaltime(d) + D30MIN) {
|
2015-03-14 14:35:47 +00:00
|
|
|
|
// this picture belongs to this dive
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-21 09:00:16 +00:00
|
|
|
|
bool picture_check_valid(const char *filename, int shift_time)
|
2015-03-14 14:35:47 +00:00
|
|
|
|
{
|
|
|
|
|
int i;
|
2015-04-24 12:29:52 +00:00
|
|
|
|
struct dive *dive;
|
2015-03-14 14:35:47 +00:00
|
|
|
|
|
2015-04-24 12:29:52 +00:00
|
|
|
|
timestamp_t timestamp = picture_get_timestamp(filename);
|
|
|
|
|
for_each_dive (i, dive)
|
2015-04-24 15:10:55 +00:00
|
|
|
|
if (dive->selected && dive_check_picture_time(dive, shift_time, timestamp))
|
2015-04-24 12:29:52 +00:00
|
|
|
|
return true;
|
|
|
|
|
return false;
|
2015-03-14 14:35:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-21 09:00:16 +00:00
|
|
|
|
void dive_create_picture(struct dive *dive, const char *filename, int shift_time, bool match_all)
|
2014-06-03 17:51:47 +00:00
|
|
|
|
{
|
2015-04-24 12:29:52 +00:00
|
|
|
|
timestamp_t timestamp = picture_get_timestamp(filename);
|
2015-04-24 12:19:41 +00:00
|
|
|
|
if (!new_picture_for_dive(dive, filename))
|
2014-06-08 18:55:03 +00:00
|
|
|
|
return;
|
2015-09-11 09:31:02 +00:00
|
|
|
|
if (!match_all && !dive_check_picture_time(dive, shift_time, timestamp))
|
2015-03-14 14:35:47 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2015-04-24 12:19:41 +00:00
|
|
|
|
struct picture *picture = alloc_picture();
|
2015-04-24 15:10:55 +00:00
|
|
|
|
picture->filename = strdup(filename);
|
2015-04-24 12:29:52 +00:00
|
|
|
|
picture->offset.seconds = timestamp - dive->when + shift_time;
|
2015-04-24 12:19:41 +00:00
|
|
|
|
picture_load_exif_data(picture);
|
2015-03-14 14:35:47 +00:00
|
|
|
|
|
2015-04-24 12:19:41 +00:00
|
|
|
|
dive_add_picture(dive, picture);
|
|
|
|
|
dive_set_geodata_from_picture(dive, picture);
|
2016-04-03 22:31:59 +00:00
|
|
|
|
invalidate_dive_cache(dive);
|
2014-06-03 17:51:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-24 12:19:41 +00:00
|
|
|
|
void dive_add_picture(struct dive *dive, struct picture *newpic)
|
2014-06-08 19:34:18 +00:00
|
|
|
|
{
|
2015-04-24 12:19:41 +00:00
|
|
|
|
struct picture **pic_ptr = &dive->picture_list;
|
2014-06-08 19:34:18 +00:00
|
|
|
|
/* let's keep the list sorted by time */
|
2015-02-02 16:45:56 +00:00
|
|
|
|
while (*pic_ptr && (*pic_ptr)->offset.seconds <= newpic->offset.seconds)
|
2014-06-08 19:34:18 +00:00
|
|
|
|
pic_ptr = &(*pic_ptr)->next;
|
|
|
|
|
newpic->next = *pic_ptr;
|
|
|
|
|
*pic_ptr = newpic;
|
2015-09-17 14:56:58 +00:00
|
|
|
|
cache_picture(newpic);
|
2014-06-02 21:28:02 +00:00
|
|
|
|
return;
|
2014-06-02 19:56:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-24 12:19:41 +00:00
|
|
|
|
unsigned int dive_get_picture_count(struct dive *dive)
|
2014-06-02 19:56:02 +00:00
|
|
|
|
{
|
2014-06-03 07:10:39 +00:00
|
|
|
|
unsigned int i = 0;
|
2015-04-24 12:19:41 +00:00
|
|
|
|
FOR_EACH_PICTURE (dive)
|
2014-06-02 20:07:26 +00:00
|
|
|
|
i++;
|
|
|
|
|
return i;
|
2014-06-02 19:56:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-24 12:19:41 +00:00
|
|
|
|
void dive_set_geodata_from_picture(struct dive *dive, struct picture *picture)
|
2014-06-02 21:28:02 +00:00
|
|
|
|
{
|
2015-04-24 12:19:41 +00:00
|
|
|
|
struct dive_site *ds = get_dive_site_by_uuid(dive->dive_site_uuid);
|
|
|
|
|
if (!dive_site_has_gps_location(ds) && (picture->latitude.udeg || picture->longitude.udeg)) {
|
2015-03-02 14:14:55 +00:00
|
|
|
|
if (ds) {
|
2015-04-24 12:19:41 +00:00
|
|
|
|
ds->latitude = picture->latitude;
|
|
|
|
|
ds->longitude = picture->longitude;
|
2015-03-02 14:14:55 +00:00
|
|
|
|
} else {
|
2015-08-24 17:37:18 +00:00
|
|
|
|
dive->dive_site_uuid = create_dive_site_with_gps("", picture->latitude, picture->longitude, dive->when);
|
2016-04-03 22:31:59 +00:00
|
|
|
|
invalidate_dive_cache(dive);
|
2015-03-02 14:14:55 +00:00
|
|
|
|
}
|
2014-06-02 21:28:02 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-15 20:31:59 +00:00
|
|
|
|
void picture_free(struct picture *picture)
|
2014-10-11 11:25:52 +00:00
|
|
|
|
{
|
2015-04-24 12:19:41 +00:00
|
|
|
|
if (!picture)
|
2014-07-30 02:03:32 +00:00
|
|
|
|
return;
|
2015-04-24 12:19:41 +00:00
|
|
|
|
free(picture->filename);
|
|
|
|
|
free(picture->hash);
|
|
|
|
|
free(picture);
|
2014-07-30 02:03:32 +00:00
|
|
|
|
}
|
2015-03-14 14:35:47 +00:00
|
|
|
|
|
2016-03-15 20:31:59 +00:00
|
|
|
|
// When handling pictures in different threads, we need to copy them so we don't
|
|
|
|
|
// run into problems when the main thread frees the picture.
|
|
|
|
|
|
|
|
|
|
struct picture *clone_picture(struct picture *src)
|
|
|
|
|
{
|
|
|
|
|
struct picture *dst;
|
|
|
|
|
|
|
|
|
|
dst = alloc_picture();
|
|
|
|
|
copy_pl(src, dst);
|
|
|
|
|
return dst;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-05 19:37:14 +00:00
|
|
|
|
void dive_remove_picture(char *filename)
|
2014-06-02 19:56:02 +00:00
|
|
|
|
{
|
2015-04-24 12:19:41 +00:00
|
|
|
|
struct picture **picture = ¤t_dive->picture_list;
|
|
|
|
|
while (picture && !same_string((*picture)->filename, filename))
|
|
|
|
|
picture = &(*picture)->next;
|
|
|
|
|
if (picture) {
|
|
|
|
|
struct picture *temp = (*picture)->next;
|
|
|
|
|
picture_free(*picture);
|
|
|
|
|
*picture = temp;
|
2016-04-03 22:31:59 +00:00
|
|
|
|
invalidate_dive_cache(current_dive);
|
2014-07-30 02:03:32 +00:00
|
|
|
|
}
|
2014-06-02 19:56:02 +00:00
|
|
|
|
}
|
2014-06-11 20:56:33 +00:00
|
|
|
|
|
|
|
|
|
/* this always acts on the current divecomputer of the current dive */
|
|
|
|
|
void make_first_dc()
|
|
|
|
|
{
|
|
|
|
|
struct divecomputer *dc = ¤t_dive->dc;
|
|
|
|
|
struct divecomputer *newdc = malloc(sizeof(*newdc));
|
|
|
|
|
struct divecomputer *cur_dc = current_dc; /* needs to be in a local variable so the macro isn't re-executed */
|
|
|
|
|
|
|
|
|
|
/* skip the current DC in the linked list */
|
|
|
|
|
while (dc && dc->next != cur_dc)
|
|
|
|
|
dc = dc->next;
|
|
|
|
|
if (!dc) {
|
2014-07-10 20:11:42 +00:00
|
|
|
|
free(newdc);
|
2014-06-11 20:56:33 +00:00
|
|
|
|
fprintf(stderr, "data inconsistent: can't find the current DC");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
dc->next = cur_dc->next;
|
|
|
|
|
*newdc = current_dive->dc;
|
|
|
|
|
current_dive->dc = *cur_dc;
|
|
|
|
|
current_dive->dc.next = newdc;
|
|
|
|
|
free(cur_dc);
|
2016-04-03 22:31:59 +00:00
|
|
|
|
invalidate_dive_cache(current_dive);
|
2014-06-11 20:56:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* always acts on the current dive */
|
2016-03-10 02:18:58 +00:00
|
|
|
|
unsigned int count_divecomputers(void)
|
2014-06-11 20:56:33 +00:00
|
|
|
|
{
|
|
|
|
|
int ret = 1;
|
|
|
|
|
struct divecomputer *dc = current_dive->dc.next;
|
|
|
|
|
while (dc) {
|
|
|
|
|
ret++;
|
|
|
|
|
dc = dc->next;
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* always acts on the current dive */
|
|
|
|
|
void delete_current_divecomputer(void)
|
|
|
|
|
{
|
|
|
|
|
struct divecomputer *dc = current_dc;
|
|
|
|
|
|
|
|
|
|
if (dc == ¤t_dive->dc) {
|
|
|
|
|
/* remove the first one, so copy the second one in place of the first and free the second one
|
|
|
|
|
* be careful about freeing the no longer needed structures - since we copy things around we can't use free_dc()*/
|
|
|
|
|
struct divecomputer *fdc = dc->next;
|
|
|
|
|
free(dc->sample);
|
|
|
|
|
free((void *)dc->model);
|
|
|
|
|
free_events(dc->events);
|
|
|
|
|
memcpy(dc, fdc, sizeof(struct divecomputer));
|
|
|
|
|
free(fdc);
|
|
|
|
|
} else {
|
|
|
|
|
struct divecomputer *pdc = ¤t_dive->dc;
|
|
|
|
|
while (pdc->next != dc && pdc->next)
|
|
|
|
|
pdc = pdc->next;
|
|
|
|
|
if (pdc->next == dc) {
|
|
|
|
|
pdc->next = dc->next;
|
|
|
|
|
free_dc(dc);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (dc_number == count_divecomputers())
|
|
|
|
|
dc_number--;
|
2016-04-03 22:31:59 +00:00
|
|
|
|
invalidate_dive_cache(current_dive);
|
2014-06-11 20:56:33 +00:00
|
|
|
|
}
|
2015-07-04 16:12:54 +00:00
|
|
|
|
|
|
|
|
|
/* 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 */
|
2016-03-10 02:18:58 +00:00
|
|
|
|
int get_depth_at_time(struct divecomputer *dc, unsigned int time)
|
2015-07-04 16:12:54 +00:00
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
2016-05-21 09:32:07 +00:00
|
|
|
|
|
|
|
|
|
//Calculate O2 in best mix
|
|
|
|
|
fraction_t best_o2(depth_t depth, struct dive *dive)
|
|
|
|
|
{
|
|
|
|
|
fraction_t fo2;
|
|
|
|
|
|
|
|
|
|
fo2.permille = (prefs.bottompo2 * 100 / depth_to_mbar(depth.mm, dive)) * 10; //use integer arithmetic to round down to nearest percent
|
2016-07-06 12:40:34 +00:00
|
|
|
|
// Don't permit >100% O2
|
|
|
|
|
if (fo2.permille > 1000)
|
|
|
|
|
fo2.permille = 1000;
|
2016-05-21 09:32:07 +00:00
|
|
|
|
return fo2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Calculate He in best mix. O2 is considered narcopic
|
2016-07-06 12:40:36 +00:00
|
|
|
|
fraction_t best_he(depth_t depth, struct dive *dive)
|
2016-05-21 09:32:07 +00:00
|
|
|
|
{
|
|
|
|
|
fraction_t fhe;
|
|
|
|
|
int pnarcotic, ambient;
|
2016-07-06 12:40:31 +00:00
|
|
|
|
pnarcotic = depth_to_mbar(prefs.bestmixend.mm, dive);
|
2016-05-21 09:32:07 +00:00
|
|
|
|
ambient = depth_to_mbar(depth.mm, dive);
|
|
|
|
|
fhe.permille = (100 - 100 * pnarcotic / ambient) * 10; //use integer arithmetic to round up to nearest percent
|
|
|
|
|
if (fhe.permille < 0)
|
|
|
|
|
fhe.permille = 0;
|
|
|
|
|
return fhe;
|
|
|
|
|
}
|