2017-04-27 18:24:53 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2024-02-27 11:02:20 +00:00
|
|
|
/* dive.cpp */
|
2011-09-20 19:40:34 +00:00
|
|
|
/* 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>
|
2024-02-27 12:24:36 +00:00
|
|
|
#include <memory>
|
2020-05-01 11:43:52 +00:00
|
|
|
#include "dive.h"
|
2013-10-06 15:55:58 +00:00
|
|
|
#include "gettext.h"
|
2018-05-11 15:25:41 +00:00
|
|
|
#include "subsurface-string.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"
|
2019-03-04 22:20:29 +00:00
|
|
|
#include "divesite.h"
|
2024-06-25 05:43:32 +00:00
|
|
|
#include "equipment.h"
|
2020-03-04 18:29:59 +00:00
|
|
|
#include "errorhelper.h"
|
2020-10-25 08:14:16 +00:00
|
|
|
#include "event.h"
|
2020-10-25 12:28:55 +00:00
|
|
|
#include "extradata.h"
|
2024-05-28 19:31:11 +00:00
|
|
|
#include "format.h"
|
2024-06-25 15:07:24 +00:00
|
|
|
#include "fulltext.h"
|
2020-10-25 17:14:23 +00:00
|
|
|
#include "interpolate.h"
|
2018-02-24 22:28:13 +00:00
|
|
|
#include "qthelper.h"
|
2018-04-09 08:09:34 +00:00
|
|
|
#include "membuffer.h"
|
2020-04-10 07:42:14 +00:00
|
|
|
#include "picture.h"
|
2024-05-18 19:04:58 +00:00
|
|
|
#include "range.h"
|
2020-10-25 12:28:55 +00:00
|
|
|
#include "sample.h"
|
2019-05-30 16:29:36 +00:00
|
|
|
#include "tag.h"
|
2019-05-31 14:09:14 +00:00
|
|
|
#include "trip.h"
|
2020-02-16 21:26:47 +00:00
|
|
|
|
2018-06-16 14:30:03 +00:00
|
|
|
// For user visible text but still not translated
|
2018-05-17 08:04:41 +00:00
|
|
|
const char *divemode_text_ui[] = {
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "Open circuit"),
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "CCR"),
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "pSCR"),
|
|
|
|
QT_TRANSLATE_NOOP("gettextFromC", "Freedive")
|
|
|
|
};
|
|
|
|
|
|
|
|
// For writing/reading files.
|
2018-06-04 14:30:00 +00:00
|
|
|
const char *divemode_text[] = {"OC", "CCR", "PSCR", "Freedive"};
|
2014-11-16 22:11:34 +00:00
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
// Even for dives without divecomputer, we allocate a divecomputer structure.
|
2024-06-21 14:43:27 +00:00
|
|
|
// It's the "manually added" divecomputer.
|
2024-05-27 15:09:48 +00:00
|
|
|
dive::dive() : dcs(1)
|
2024-05-16 18:11:21 +00:00
|
|
|
{
|
|
|
|
id = dive_getUniqID();
|
|
|
|
}
|
|
|
|
|
2024-06-03 19:50:08 +00:00
|
|
|
dive::dive(const dive &) = default;
|
2024-05-19 10:38:38 +00:00
|
|
|
dive::dive(dive &&) = default;
|
|
|
|
dive &dive::operator=(const dive &) = default;
|
2024-06-03 17:09:43 +00:00
|
|
|
dive::~dive() = default;
|
2024-05-19 10:38:38 +00:00
|
|
|
|
2024-06-25 05:43:32 +00:00
|
|
|
/* get_cylinder_idx_by_use(): Find the index of the first cylinder with a particular CCR use type.
|
|
|
|
* 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} */
|
|
|
|
static int get_cylinder_idx_by_use(const struct dive &dive, enum cylinderuse cylinder_use_type)
|
|
|
|
{
|
|
|
|
auto it = std::find_if(dive.cylinders.begin(), dive.cylinders.end(), [cylinder_use_type]
|
|
|
|
(auto &cyl) { return cyl.cylinder_use == cylinder_use_type; });
|
|
|
|
return it != dive.cylinders.end() ? it - dive.cylinders.begin() : -1;
|
|
|
|
}
|
|
|
|
|
2017-07-26 01:33:10 +00:00
|
|
|
/*
|
|
|
|
* 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
|
2020-03-11 10:30:51 +00:00
|
|
|
* cylinder indices for each sample.
|
2017-07-26 01:33:10 +00:00
|
|
|
*
|
|
|
|
* This function returns a negative number for "no legacy mode",
|
|
|
|
* or a non-negative number that indicates the o2 sensor index.
|
|
|
|
*/
|
2024-05-04 16:45:55 +00:00
|
|
|
int legacy_format_o2pressures(const struct dive *dive, const struct divecomputer *dc)
|
2017-07-26 01:33:10 +00:00
|
|
|
{
|
2024-05-19 10:38:38 +00:00
|
|
|
int o2sensor;
|
2017-07-26 01:33:10 +00:00
|
|
|
|
2024-06-25 05:43:32 +00:00
|
|
|
o2sensor = (dc->divemode == CCR) ? get_cylinder_idx_by_use(*dive, OXYGEN) : -1;
|
2024-05-19 10:38:38 +00:00
|
|
|
for (const auto &s: dc->samples) {
|
2017-07-26 01:33:10 +00:00
|
|
|
int seen_pressure = 0, idx;
|
|
|
|
|
|
|
|
for (idx = 0; idx < MAX_SENSORS; idx++) {
|
2024-05-19 10:38:38 +00:00
|
|
|
int sensor = s.sensor[idx];
|
|
|
|
pressure_t p = s.pressure[idx];
|
2017-07-26 01:33:10 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-06-25 05:43:32 +00:00
|
|
|
/* access to cylinders is controlled by two functions:
|
|
|
|
* - get_cylinder() returns the cylinder of a dive and supposes that
|
|
|
|
* the cylinder with the given index exists. If it doesn't, an error
|
|
|
|
* message is printed and the "surface air" cylinder returned.
|
|
|
|
* (NOTE: this MUST not be written into!).
|
|
|
|
* - get_or_create_cylinder() creates an empty cylinder if it doesn't exist.
|
|
|
|
* Multiple cylinders might be created if the index is bigger than the
|
|
|
|
* number of existing cylinders
|
|
|
|
*/
|
|
|
|
cylinder_t *dive::get_cylinder(int idx)
|
|
|
|
{
|
|
|
|
return &cylinders[idx];
|
|
|
|
}
|
|
|
|
|
|
|
|
const cylinder_t *dive::get_cylinder(int idx) const
|
|
|
|
{
|
|
|
|
return &cylinders[idx];
|
|
|
|
}
|
|
|
|
|
2020-03-04 18:41:40 +00:00
|
|
|
/* warning: does not test idx for validity */
|
2024-05-25 06:16:57 +00:00
|
|
|
struct event create_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx)
|
2020-03-04 18:41:40 +00:00
|
|
|
{
|
|
|
|
/* The gas switch event format is insane for historical reasons */
|
2024-06-25 05:43:32 +00:00
|
|
|
struct gasmix mix = dive->get_cylinder(idx)->gasmix;
|
2020-03-04 18:41:40 +00:00
|
|
|
int o2 = get_o2(mix);
|
|
|
|
int he = get_he(mix);
|
|
|
|
|
|
|
|
o2 = (o2 + 5) / 10;
|
|
|
|
he = (he + 5) / 10;
|
2024-05-25 06:16:57 +00:00
|
|
|
int value = o2 + (he << 16);
|
2020-03-04 18:41:40 +00:00
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
struct event ev(seconds, he ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE, 0, value, "gaschange");
|
|
|
|
ev.gas.index = idx;
|
|
|
|
ev.gas.mix = mix;
|
2020-03-04 18:41:40 +00:00
|
|
|
return ev;
|
|
|
|
}
|
|
|
|
|
2024-05-04 16:45:55 +00:00
|
|
|
void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx)
|
2020-03-04 18:29:59 +00:00
|
|
|
{
|
|
|
|
/* sanity check so we don't crash */
|
2020-04-27 19:12:24 +00:00
|
|
|
/* FIXME: The planner uses a dummy cylinder one past the official number of cylinders
|
|
|
|
* in the table to mark no-cylinder surface interavals. This is horrendous. Fix ASAP. */
|
2024-05-28 19:31:11 +00:00
|
|
|
//if (idx < 0 || idx >= dive->cylinders.size()) {
|
|
|
|
if (idx < 0 || static_cast<size_t>(idx) >= dive->cylinders.size() + 1) {
|
2020-03-04 18:29:59 +00:00
|
|
|
report_error("Unknown cylinder index: %d", idx);
|
|
|
|
return;
|
|
|
|
}
|
2024-05-25 06:16:57 +00:00
|
|
|
struct event ev = create_gas_switch_event(dive, dc, seconds, idx);
|
|
|
|
add_event_to_dc(dc, std::move(ev));
|
2020-03-04 18:29:59 +00:00
|
|
|
}
|
|
|
|
|
2024-06-25 05:43:32 +00:00
|
|
|
struct gasmix dive::get_gasmix_from_event(const struct event &ev) const
|
2014-06-01 19:07:29 +00:00
|
|
|
{
|
2024-05-25 18:12:10 +00:00
|
|
|
if (ev.is_gaschange()) {
|
2024-05-25 06:16:57 +00:00
|
|
|
int index = ev.gas.index;
|
2020-04-30 22:56:44 +00:00
|
|
|
// FIXME: The planner uses one past cylinder-count to signify "surface air". Remove in due course.
|
2024-06-25 05:43:32 +00:00
|
|
|
if (index >= 0 && static_cast<size_t>(index) < cylinders.size() + 1)
|
|
|
|
return get_cylinder(index)->gasmix;
|
2024-05-25 06:16:57 +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
|
|
|
}
|
2020-04-30 22:56:44 +00:00
|
|
|
return gasmix_air;
|
2014-06-01 19:07:29 +00:00
|
|
|
}
|
|
|
|
|
2018-07-17 21:05:03 +00:00
|
|
|
// we need this to be uniq. oh, and it has no meaning whatsoever
|
|
|
|
// - that's why we have the silly initial number and increment by 3 :-)
|
2024-05-04 16:45:55 +00:00
|
|
|
int dive_getUniqID()
|
2018-07-17 21:05:03 +00:00
|
|
|
{
|
|
|
|
static int maxId = 83529;
|
|
|
|
maxId += 3;
|
|
|
|
return maxId;
|
|
|
|
}
|
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
static void dc_cylinder_renumber(struct dive &dive, struct divecomputer &dc, const int mapping[]);
|
2018-08-13 02:47:07 +00:00
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
/* copy dive computer list and renumber the cylinders */
|
2024-06-04 11:22:25 +00:00
|
|
|
static void copy_dc_renumber(struct dive &d, const struct dive &s, const int cylinders_map[])
|
2018-08-13 02:47:07 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
for (const divecomputer &dc: s.dcs) {
|
|
|
|
d.dcs.push_back(dc);
|
|
|
|
dc_cylinder_renumber(d, d.dcs.back(), cylinders_map);
|
2018-10-15 19:17:23 +00:00
|
|
|
}
|
2018-08-13 02:47:07 +00:00
|
|
|
}
|
|
|
|
|
2024-06-25 15:07:24 +00:00
|
|
|
void dive::clear()
|
2018-09-27 19:55:03 +00:00
|
|
|
{
|
2024-06-25 15:07:24 +00:00
|
|
|
*this = dive();
|
2014-07-02 22:29:02 +00:00
|
|
|
}
|
|
|
|
|
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 */
|
2024-05-27 15:09:48 +00:00
|
|
|
void copy_dive(const struct dive *s, struct dive *d)
|
2014-07-02 22:29:02 +00:00
|
|
|
{
|
2024-06-25 15:07:24 +00:00
|
|
|
/* simply copy things over, but then the dive cache. */
|
2014-07-02 22:29:02 +00:00
|
|
|
*d = *s;
|
2024-06-25 12:04:01 +00:00
|
|
|
d->invalidate_cache();
|
2019-03-31 08:20:13 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 20:16:08 +00:00
|
|
|
/* copies all events from the given dive computer before a given time
|
2020-04-12 11:39:01 +00:00
|
|
|
this is used when editing a dive in the planner to preserve the events
|
|
|
|
of the old dive */
|
2024-05-04 16:45:55 +00:00
|
|
|
void copy_events_until(const struct dive *sd, struct dive *dd, int dcNr, int time)
|
2020-04-12 11:39:01 +00:00
|
|
|
{
|
|
|
|
if (!sd || !dd)
|
|
|
|
return;
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
const struct divecomputer *s = &sd->dcs[0];
|
2024-06-30 18:38:12 +00:00
|
|
|
struct divecomputer *d = dd->get_dc(dcNr);
|
2024-04-25 20:16:08 +00:00
|
|
|
|
|
|
|
if (!s || !d)
|
|
|
|
return;
|
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
for (const auto &ev: s->events) {
|
2024-04-25 20:16:08 +00:00
|
|
|
// Don't add events the planner knows about
|
2024-05-25 18:12:10 +00:00
|
|
|
if (ev.time.seconds < time && !ev.is_gaschange() && !ev.is_divemodechange())
|
2024-05-25 06:16:57 +00:00
|
|
|
add_event(d, ev.time.seconds, ev.type, ev.flags, ev.value, ev.name);
|
2020-04-12 11:39:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-04 16:45:55 +00:00
|
|
|
void copy_used_cylinders(const struct dive *s, struct dive *d, bool used_only)
|
Core: split copy_cylinders() in two functions
copy_cylinders() copied the cylinders of one dive onto another dive
and then reset to the original gas values. Presumably, when copy and
pasting cylinders from one dive to another, only the types should
be copied, not the gases.
Moreover, the function could either copy all or only the used cylinders.
Firstly, the code was bogus: when restoring the pressures the indices
were mixed up: the old indices were used. Thus, when there where
uncopied cylinders, not all pressure values were restored.
Secondly, it is not clear that all callers actually want to restore
the pressure data. It rather appears the two (out of three) callers
actually just want to copy the cylinders.
Therefore, split the function in
1) copy_cylinders(): copy the cylinders with pressure data
2) copy_cylinder_types(): copy only the cylinder information
Since there is only one caller of copy_cylinder_types(), the "used_only"
argument can be removed. Since all cylinders are copied there is
no point in storing the pressure data. Don't overwrite it in
the first place.
The resulting two functions should be distinctly easier to understand.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2019-07-25 20:29:07 +00:00
|
|
|
{
|
|
|
|
if (!s || !d)
|
|
|
|
return;
|
|
|
|
|
2024-05-28 19:31:11 +00:00
|
|
|
d->cylinders.clear();
|
|
|
|
for (auto [i, cyl]: enumerated_range(s->cylinders)) {
|
2024-06-25 05:43:32 +00:00
|
|
|
if (!used_only || s->is_cylinder_used(i) || s->get_cylinder(i)->cylinder_use == NOT_USED)
|
2024-05-28 19:31:11 +00:00
|
|
|
d->cylinders.push_back(cyl);
|
Core: split copy_cylinders() in two functions
copy_cylinders() copied the cylinders of one dive onto another dive
and then reset to the original gas values. Presumably, when copy and
pasting cylinders from one dive to another, only the types should
be copied, not the gases.
Moreover, the function could either copy all or only the used cylinders.
Firstly, the code was bogus: when restoring the pressures the indices
were mixed up: the old indices were used. Thus, when there where
uncopied cylinders, not all pressure values were restored.
Secondly, it is not clear that all callers actually want to restore
the pressure data. It rather appears the two (out of three) callers
actually just want to copy the cylinders.
Therefore, split the function in
1) copy_cylinders(): copy the cylinders with pressure data
2) copy_cylinder_types(): copy only the cylinder information
Since there is only one caller of copy_cylinder_types(), the "used_only"
argument can be removed. Since all cylinders are copied there is
no point in storing the pressure data. Don't overwrite it in
the first place.
The resulting two functions should be distinctly easier to understand.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2019-07-25 20:29:07 +00:00
|
|
|
}
|
2013-11-07 08:25:42 +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.
|
|
|
|
*/
|
2024-02-27 11:02:20 +00:00
|
|
|
static void update_depth(depth_t *depth, int new_depth)
|
2011-09-03 20:36:25 +00:00
|
|
|
{
|
2024-02-27 11:02:20 +00:00
|
|
|
if (new_depth) {
|
2011-09-04 20:06:47 +00:00
|
|
|
int old = depth->mm;
|
2011-09-03 20:36:25 +00:00
|
|
|
|
2024-02-27 11:02:20 +00:00
|
|
|
if (abs(old - new_depth) > 1000)
|
|
|
|
depth->mm = new_depth;
|
2011-09-04 20:06:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-27 11:02:20 +00:00
|
|
|
static void update_temperature(temperature_t *temperature, int new_temp)
|
2011-09-04 20:06:47 +00:00
|
|
|
{
|
2024-02-27 11:02:20 +00:00
|
|
|
if (new_temp) {
|
2011-09-04 20:06:47 +00:00
|
|
|
int old = temperature->mkelvin;
|
|
|
|
|
2024-02-27 11:02:20 +00:00
|
|
|
if (abs(old - new_temp) > 1000)
|
|
|
|
temperature->mkelvin = new_temp;
|
2011-09-04 20:06:47 +00:00
|
|
|
}
|
2011-09-03 20:36:25 +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
|
2024-05-28 19:31:11 +00:00
|
|
|
static bool cylinder_used(const cylinder_t &cyl)
|
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
|
|
|
{
|
2019-07-15 19:34:39 +00:00
|
|
|
int start_mbar, end_mbar;
|
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
|
|
|
|
2024-05-28 19:31:11 +00:00
|
|
|
start_mbar = cyl.start.mbar ?: cyl.sample_start.mbar;
|
|
|
|
end_mbar = cyl.end.mbar ?: cyl.sample_end.mbar;
|
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
|
|
|
|
2024-05-01 21:16:00 +00:00
|
|
|
// More than 5 bar used? This matches statistics.cpp
|
2019-07-15 19:34:39 +00:00
|
|
|
// heuristics
|
|
|
|
return start_mbar > end_mbar + SOME_GAS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get list of used cylinders. Returns the number of used cylinders. */
|
|
|
|
static int get_cylinder_used(const struct dive *dive, bool used[])
|
|
|
|
{
|
2024-05-28 19:31:11 +00:00
|
|
|
int num = 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
|
|
|
|
2024-05-28 19:31:11 +00:00
|
|
|
for (auto [i, cyl]: enumerated_range(dive->cylinders)) {
|
|
|
|
used[i] = cylinder_used(cyl);
|
2019-07-15 19:34:39 +00:00
|
|
|
if (used[i])
|
|
|
|
num++;
|
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
|
|
|
}
|
2019-07-15 19:34:39 +00:00
|
|
|
return num;
|
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
|
|
|
}
|
|
|
|
|
2024-06-25 05:43:32 +00:00
|
|
|
/*
|
|
|
|
* If the event has an explicit cylinder index,
|
|
|
|
* we return that. If it doesn't, we return the best
|
|
|
|
* match based on the gasmix.
|
|
|
|
*
|
|
|
|
* Some dive computers give cylinder indices, some
|
|
|
|
* give just the gas mix.
|
|
|
|
*/
|
|
|
|
int dive::get_cylinder_index(const struct event &ev) const
|
|
|
|
{
|
|
|
|
if (ev.gas.index >= 0)
|
|
|
|
return ev.gas.index;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This should no longer happen!
|
|
|
|
*
|
|
|
|
* We now match up gas change events with their cylinders at dive
|
|
|
|
* event fixup time.
|
|
|
|
*/
|
|
|
|
report_info("Still looking up cylinder based on gas mix in get_cylinder_index()!");
|
|
|
|
|
|
|
|
gasmix mix = get_gasmix_from_event(ev);
|
|
|
|
int best = find_best_gasmix_match(mix, cylinders);
|
|
|
|
return best < 0 ? 0 : best;
|
|
|
|
}
|
|
|
|
|
2024-06-30 20:39:52 +00:00
|
|
|
cylinder_t *dive::get_or_create_cylinder(int idx)
|
|
|
|
{
|
|
|
|
if (idx < 0) {
|
|
|
|
report_info("Warning: accessing invalid cylinder %d", idx);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
while (static_cast<size_t>(idx) >= cylinders.size())
|
2024-07-01 19:13:12 +00:00
|
|
|
cylinders.emplace_back();
|
2024-06-30 20:39:52 +00:00
|
|
|
return &cylinders[idx];
|
|
|
|
}
|
|
|
|
|
2019-07-15 19:34:39 +00:00
|
|
|
/* Are there any used cylinders which we do not know usage about? */
|
2024-06-25 05:43:32 +00:00
|
|
|
static bool has_unknown_used_cylinders(const struct dive &dive, const struct divecomputer *dc,
|
2019-07-15 19:34:39 +00:00
|
|
|
const bool used_cylinders[], int num)
|
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
|
|
|
{
|
2019-07-15 19:34:39 +00:00
|
|
|
int idx;
|
2024-06-25 05:43:32 +00:00
|
|
|
auto used_and_unknown = std::make_unique<bool[]>(dive.cylinders.size());
|
|
|
|
std::copy(used_cylinders, used_cylinders + dive.cylinders.size(), used_and_unknown.get());
|
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
|
|
|
|
|
|
|
/* 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);
|
2019-07-15 19:34:39 +00:00
|
|
|
if (o2_cyl >= 0 && used_and_unknown[o2_cyl]) {
|
|
|
|
used_and_unknown[o2_cyl] = false;
|
|
|
|
num--;
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/* We know about the explicit first cylinder (or first) */
|
2024-06-25 05:43:32 +00:00
|
|
|
idx = dive.explicit_first_cylinder(dc);
|
2024-03-13 00:11:34 +00:00
|
|
|
if (idx >= 0 && used_and_unknown[idx]) {
|
2019-07-15 19:34:39 +00:00
|
|
|
used_and_unknown[idx] = false;
|
|
|
|
num--;
|
|
|
|
}
|
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
|
|
|
|
|
|
|
/* And we have possible switches to other gases */
|
2024-05-25 06:16:57 +00:00
|
|
|
event_loop loop("gaschange");
|
|
|
|
const struct event *ev;
|
|
|
|
while ((ev = loop.next(*dc)) != nullptr && num > 0) {
|
2024-06-25 05:43:32 +00:00
|
|
|
idx = dive.get_cylinder_index(*ev);
|
2019-07-15 19:34:39 +00:00
|
|
|
if (idx >= 0 && used_and_unknown[idx]) {
|
|
|
|
used_and_unknown[idx] = false;
|
|
|
|
num--;
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2019-07-15 19:34:39 +00:00
|
|
|
return num > 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
|
|
|
}
|
|
|
|
|
2024-05-04 16:45:55 +00:00
|
|
|
void per_cylinder_mean_depth(const struct dive *dive, struct divecomputer *dc, int *mean, int *duration)
|
2013-11-20 06:50:02 +00:00
|
|
|
{
|
2024-02-27 11:02:20 +00:00
|
|
|
int32_t lasttime = 0;
|
2016-03-10 02:18:58 +00:00
|
|
|
int lastdepth = 0;
|
2013-11-20 06:50:02 +00:00
|
|
|
int idx = 0;
|
2019-07-15 19:34:39 +00:00
|
|
|
int num_used_cylinders;
|
2013-11-20 06:50:02 +00:00
|
|
|
|
2024-05-28 19:31:11 +00:00
|
|
|
if (dive->cylinders.empty())
|
2019-08-04 17:49:43 +00:00
|
|
|
return;
|
|
|
|
|
2024-05-28 19:31:11 +00:00
|
|
|
for (size_t i = 0; i < dive->cylinders.size(); i++)
|
2013-11-20 06:50:02 +00:00
|
|
|
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.
|
|
|
|
*/
|
2024-05-28 19:31:11 +00:00
|
|
|
auto used_cylinders = std::make_unique<bool[]>(dive->cylinders.size());
|
2024-02-27 12:24:36 +00:00
|
|
|
num_used_cylinders = get_cylinder_used(dive, used_cylinders.get());
|
2024-06-25 05:43:32 +00:00
|
|
|
if (has_unknown_used_cylinders(*dive, dc, used_cylinders.get(), num_used_cylinders)) {
|
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
|
|
|
/*
|
|
|
|
* If we had more than one used cylinder, but
|
|
|
|
* do not know usage of them, we simply cannot
|
|
|
|
* account mean depth to them.
|
|
|
|
*/
|
2024-02-27 12:24:36 +00:00
|
|
|
if (num_used_cylinders > 1)
|
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
|
|
|
return;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For a single cylinder, use the overall mean
|
|
|
|
* and duration
|
|
|
|
*/
|
2024-05-28 19:31:11 +00:00
|
|
|
for (size_t i = 0; i < dive->cylinders.size(); i++) {
|
2019-07-15 19:34:39 +00:00
|
|
|
if (used_cylinders[i]) {
|
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
|
|
|
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;
|
|
|
|
}
|
2024-05-19 10:38:38 +00:00
|
|
|
if (dc->samples.empty())
|
2018-05-05 17:26:48 +00:00
|
|
|
fake_dc(dc);
|
2024-05-25 06:16:57 +00:00
|
|
|
event_loop loop("gaschange");
|
|
|
|
const struct event *ev = loop.next(*dc);
|
2024-05-28 19:31:11 +00:00
|
|
|
std::vector<int> depthtime(dive->cylinders.size(), 0);
|
2024-05-19 10:38:38 +00:00
|
|
|
for (auto it = dc->samples.begin(); it != dc->samples.end(); ++it) {
|
|
|
|
int32_t time = it->time.seconds;
|
|
|
|
int depth = it->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) {
|
2024-06-25 05:43:32 +00:00
|
|
|
idx = dive->get_cylinder_index(*ev);
|
2024-05-25 06:16:57 +00:00
|
|
|
ev = loop.next(*dc);
|
2013-11-20 06:50:02 +00:00
|
|
|
}
|
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? */
|
2024-05-19 10:38:38 +00:00
|
|
|
if (ev && it != dc->samples.begin() && time > ev->time.seconds) {
|
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
|
|
|
int newtime = ev->time.seconds;
|
|
|
|
int newdepth = interpolate(lastdepth, depth, newtime - lasttime, time - lasttime);
|
|
|
|
|
|
|
|
time = newtime;
|
|
|
|
depth = newdepth;
|
2024-05-19 10:38:38 +00:00
|
|
|
--it;
|
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
|
|
|
}
|
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;
|
|
|
|
}
|
2024-05-28 19:31:11 +00:00
|
|
|
for (size_t i = 0; i < dive->cylinders.size(); i++) {
|
2013-11-20 06:50:02 +00:00
|
|
|
if (duration[i])
|
|
|
|
mean[i] = (depthtime[i] + duration[i] / 2) / duration[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +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) {
|
2024-06-23 12:20:59 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
2019-12-23 21:02:10 +00:00
|
|
|
* first cylinder - in which case cylinder 0 is indeed the first cylinder.
|
2020-04-20 19:28:17 +00:00
|
|
|
* We likewise return 0 if the event concerns a cylinder that doesn't exist.
|
|
|
|
* If the dive has no cylinders, -1 is returned. */
|
2024-06-25 05:43:32 +00:00
|
|
|
int dive::explicit_first_cylinder(const struct divecomputer *dc) const
|
2014-10-28 20:48:15 +00:00
|
|
|
{
|
2019-12-23 21:02:10 +00:00
|
|
|
int res = 0;
|
2024-06-25 05:43:32 +00:00
|
|
|
if (cylinders.empty())
|
2020-04-20 19:28:17 +00:00
|
|
|
return -1;
|
2015-06-22 04:38:12 +00:00
|
|
|
if (dc) {
|
2024-05-25 06:16:57 +00:00
|
|
|
const struct event *ev = get_first_event(*dc, "gaschange");
|
2024-05-19 10:38:38 +00:00
|
|
|
if (ev && ((!dc->samples.empty() && ev->time.seconds == dc->samples[0].time.seconds) || ev->time.seconds <= 1))
|
2024-06-25 05:43:32 +00:00
|
|
|
res = get_cylinder_index(*ev);
|
2015-06-22 04:38:12 +00:00
|
|
|
else if (dc->divemode == CCR)
|
2024-06-25 05:43:32 +00:00
|
|
|
res = std::max(get_cylinder_idx_by_use(*this, DILUENT), res);
|
2015-06-22 04:38:12 +00:00
|
|
|
}
|
2024-06-25 05:43:32 +00:00
|
|
|
return static_cast<size_t>(res) < cylinders.size() ? res : 0;
|
2014-10-28 20:48:15 +00:00
|
|
|
}
|
|
|
|
|
2024-06-20 20:09:47 +00:00
|
|
|
static double calculate_depth_to_mbarf(int depth, pressure_t surface_pressure, int salinity);
|
|
|
|
|
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
|
|
|
|
*/
|
2024-05-04 16:45:55 +00:00
|
|
|
void update_setpoint_events(const struct dive *dive, struct divecomputer *dc)
|
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 &&
|
2024-05-18 15:03:19 +00:00
|
|
|
(dc->model == "Shearwater Predator" ||
|
|
|
|
dc->model == "Shearwater Petrel" ||
|
|
|
|
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.
|
2024-05-25 06:16:57 +00:00
|
|
|
event_loop loop("gaschange");
|
|
|
|
const struct event *ev = loop.next(*dc);
|
2024-06-25 05:43:32 +00:00
|
|
|
struct gasmix gasmix = dive->get_gasmix_from_event(*ev);
|
2024-05-25 06:16:57 +00:00
|
|
|
const struct event *next = loop.next(*dc);
|
2015-02-07 20:02:02 +00:00
|
|
|
|
2024-05-19 10:38:38 +00:00
|
|
|
for (auto &sample: dc->samples) {
|
|
|
|
if (next && sample.time.seconds >= next->time.seconds) {
|
2015-02-07 20:02:02 +00:00
|
|
|
ev = next;
|
2024-06-25 05:43:32 +00:00
|
|
|
gasmix = dive->get_gasmix_from_event(*ev);
|
2024-05-25 06:16:57 +00:00
|
|
|
next = loop.next(*dc);
|
2015-02-07 20:02:02 +00:00
|
|
|
}
|
2024-05-19 10:38:38 +00:00
|
|
|
gas_pressures pressures = fill_pressures(lrint(calculate_depth_to_mbarf(sample.depth.mm, dc->surface_pressure, 0)), gasmix ,0, dc->divemode);
|
|
|
|
if (abs(sample.setpoint.mbar - (int)(1000 * pressures.o2)) <= 50)
|
|
|
|
sample.setpoint.mbar = 0;
|
2015-02-07 20:02:02 +00:00
|
|
|
}
|
|
|
|
}
|
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
|
2024-05-25 06:16:57 +00:00
|
|
|
struct event *ev = get_first_event(*dc, "SP change");
|
2015-02-07 16:31:16 +00:00
|
|
|
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"))
|
2024-03-24 20:03:08 +00:00
|
|
|
report_info("Could not add setpoint change event");
|
2015-01-08 13:42:07 +00:00
|
|
|
}
|
2015-01-01 16:00:46 +00:00
|
|
|
}
|
|
|
|
|
2011-12-30 21:09:17 +00:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
*/
|
2024-05-28 19:31:11 +00:00
|
|
|
static void match_standard_cylinder(cylinder_type_t &type)
|
2011-12-30 21:09:17 +00:00
|
|
|
{
|
|
|
|
/* Do we already have a cylinder description? */
|
2024-05-28 19:31:11 +00:00
|
|
|
if (!type.description.empty())
|
2011-12-30 21:09:17 +00:00
|
|
|
return;
|
|
|
|
|
2024-05-28 19:31:11 +00:00
|
|
|
double bar = type.workingpressure.mbar / 1000.0;
|
|
|
|
double cuft = ml_to_cuft(type.size.mliter);
|
2016-02-24 19:31:03 +00:00
|
|
|
cuft *= bar_to_atm(bar);
|
2024-05-28 19:31:11 +00:00
|
|
|
int psi = lrint(to_PSI(type.workingpressure));
|
2011-12-30 21:09:17 +00:00
|
|
|
|
2024-05-28 19:31:11 +00:00
|
|
|
const char *fmt;
|
2011-12-30 21:09:17 +00:00
|
|
|
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;
|
|
|
|
}
|
2024-05-28 19:31:11 +00:00
|
|
|
type.description = format_string_std(fmt, (int)lrint(cuft));
|
2011-12-30 21:09:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2024-05-28 19:31:11 +00:00
|
|
|
static void sanitize_cylinder_type(cylinder_type_t &type)
|
2011-12-30 21:09:17 +00:00
|
|
|
{
|
|
|
|
/* If we have no working pressure, it had *better* be just a physical size! */
|
2024-05-28 19:31:11 +00:00
|
|
|
if (!type.workingpressure.mbar)
|
2011-12-30 21:09:17 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
/* No size either? Nothing to go on */
|
2024-05-28 19:31:11 +00:00
|
|
|
if (!type.size.mliter)
|
2011-12-30 21:09:17 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
/* Ok, we have both size and pressure: try to match a description */
|
|
|
|
match_standard_cylinder(type);
|
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void sanitize_cylinder_info(struct dive &dive)
|
2011-12-30 21:09:17 +00:00
|
|
|
{
|
2024-06-23 12:20:59 +00:00
|
|
|
for (auto &cyl: dive.cylinders) {
|
2024-05-28 19:31:11 +00:00
|
|
|
sanitize_gasmix(cyl.gasmix);
|
|
|
|
sanitize_cylinder_type(cyl.type);
|
2011-12-30 21:09:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-10 10:40:35 +00:00
|
|
|
/* some events should never be thrown away */
|
2024-05-25 06:16:57 +00:00
|
|
|
static bool is_potentially_redundant(const struct event &event)
|
2012-11-10 10:40:35 +00:00
|
|
|
{
|
2024-05-25 06:16:57 +00:00
|
|
|
if (event.name == "gaschange")
|
2014-01-15 18:54:41 +00:00
|
|
|
return false;
|
2024-05-25 06:16:57 +00:00
|
|
|
if (event.name == "bookmark")
|
2014-01-15 18:54:41 +00:00
|
|
|
return false;
|
2024-05-25 06:16:57 +00:00
|
|
|
if (event.name == "heading")
|
2014-01-15 18:54:41 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
2012-11-10 10:40:35 +00:00
|
|
|
}
|
|
|
|
|
2024-06-22 18:08:47 +00:00
|
|
|
pressure_t dive::calculate_surface_pressure() const
|
2013-02-09 00:15:18 +00:00
|
|
|
{
|
2019-04-30 10:42:33 +00:00
|
|
|
pressure_t res;
|
2013-02-09 13:45:58 +00:00
|
|
|
int sum = 0, nr = 0;
|
2013-02-09 00:15:18 +00:00
|
|
|
|
2024-06-22 18:08:47 +00:00
|
|
|
bool logged = is_logged();
|
|
|
|
for (auto &dc: dcs) {
|
2024-05-27 15:09:48 +00:00
|
|
|
if ((logged || !is_dc_planner(&dc)) && dc.surface_pressure.mbar) {
|
|
|
|
sum += dc.surface_pressure.mbar;
|
2013-02-09 00:15:18 +00:00
|
|
|
nr++;
|
|
|
|
}
|
|
|
|
}
|
2019-04-30 10:42:33 +00:00
|
|
|
res.mbar = nr ? (sum + nr / 2) / nr : 0;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_surface_pressure(struct dive &dive)
|
2019-04-30 10:42:33 +00:00
|
|
|
{
|
2024-06-23 12:20:59 +00:00
|
|
|
dive.surface_pressure = dive.calculate_surface_pressure();
|
2019-04-30 10:42:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* if the surface pressure in the dive data is redundant to the calculated
|
|
|
|
* value (i.e., it was added by running fixup on the dive) return 0,
|
2019-05-15 14:42:14 +00:00
|
|
|
* otherwise return the surface pressure given in the dive */
|
2024-06-22 18:08:47 +00:00
|
|
|
pressure_t dive::un_fixup_surface_pressure() const
|
2019-04-30 10:42:33 +00:00
|
|
|
{
|
2024-06-22 18:08:47 +00:00
|
|
|
return surface_pressure.mbar == calculate_surface_pressure().mbar ?
|
|
|
|
pressure_t() : surface_pressure;
|
2013-02-09 00:15:18 +00:00
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_water_salinity(struct dive &dive)
|
2013-02-09 00:15:18 +00:00
|
|
|
{
|
2013-02-09 13:45:58 +00:00
|
|
|
int sum = 0, nr = 0;
|
2013-02-09 00:15:18 +00:00
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
bool logged = dive.is_logged();
|
|
|
|
for (auto &dc: dive.dcs) {
|
2024-05-27 15:09:48 +00:00
|
|
|
if ((logged || !is_dc_planner(&dc)) && dc.salinity) {
|
|
|
|
if (dc.salinity < 500)
|
|
|
|
dc.salinity += FRESHWATER_SALINITY;
|
|
|
|
sum += dc.salinity;
|
2013-02-09 00:15:18 +00:00
|
|
|
nr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (nr)
|
2024-06-23 12:20:59 +00:00
|
|
|
dive.salinity = (sum + nr / 2) / nr;
|
2013-02-09 00:15:18 +00:00
|
|
|
}
|
|
|
|
|
2024-06-30 16:31:43 +00:00
|
|
|
int dive::get_salinity() const
|
2020-05-04 13:54:58 +00:00
|
|
|
{
|
2024-06-30 16:31:43 +00:00
|
|
|
return user_salinity ? user_salinity : salinity;
|
2020-05-04 13:54:58 +00:00
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_meandepth(struct dive &dive)
|
2013-02-09 14:50:53 +00:00
|
|
|
{
|
|
|
|
int sum = 0, nr = 0;
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
bool logged = dive.is_logged();
|
|
|
|
for (auto &dc: dive.dcs) {
|
2024-05-27 15:09:48 +00:00
|
|
|
if ((logged || !is_dc_planner(&dc)) && dc.meandepth.mm) {
|
|
|
|
sum += dc.meandepth.mm;
|
2013-02-09 14:50:53 +00:00
|
|
|
nr++;
|
|
|
|
}
|
|
|
|
}
|
2022-08-20 15:24:45 +00:00
|
|
|
|
2013-02-09 14:50:53 +00:00
|
|
|
if (nr)
|
2024-06-23 12:20:59 +00:00
|
|
|
dive.meandepth.mm = (sum + nr / 2) / nr;
|
2013-02-09 14:50:53 +00:00
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_duration(struct dive &dive)
|
2013-02-09 15:12:30 +00:00
|
|
|
{
|
2024-06-23 12:20:59 +00:00
|
|
|
duration_t duration;
|
2013-02-09 15:12:30 +00:00
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
bool logged = dive.is_logged();
|
|
|
|
for (auto &dc: dive.dcs) {
|
2024-05-27 15:09:48 +00:00
|
|
|
if (logged || !is_dc_planner(&dc))
|
|
|
|
duration.seconds = std::max(duration.seconds, dc.duration.seconds);
|
2022-08-20 15:24:45 +00:00
|
|
|
}
|
2024-06-23 12:20:59 +00:00
|
|
|
dive.duration.seconds = duration.seconds;
|
2013-02-09 15:12:30 +00:00
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_watertemp(struct dive &dive)
|
2013-11-29 20:05:21 +00:00
|
|
|
{
|
2024-06-23 12:20:59 +00:00
|
|
|
if (!dive.watertemp.mkelvin)
|
|
|
|
dive.watertemp = dive.dc_watertemp();
|
2013-02-09 15:41:15 +00:00
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_airtemp(struct dive &dive)
|
2013-02-14 23:18:48 +00:00
|
|
|
{
|
2024-06-23 12:20:59 +00:00
|
|
|
if (!dive.airtemp.mkelvin)
|
|
|
|
dive.airtemp = dive.dc_airtemp();
|
2013-02-09 15:41:15 +00:00
|
|
|
}
|
|
|
|
|
2018-08-13 02:47:07 +00:00
|
|
|
/* if the air temperature in the dive data is redundant to the one in its
|
|
|
|
* first divecomputer (i.e., it was added by running fixup on the dive)
|
|
|
|
* return 0, otherwise return the air temperature given in the dive */
|
2024-06-04 11:22:25 +00:00
|
|
|
static temperature_t un_fixup_airtemp(const struct dive &a)
|
2013-02-14 17:44:18 +00:00
|
|
|
{
|
2024-06-05 15:02:40 +00:00
|
|
|
return a.airtemp.mkelvin == a.dc_airtemp().mkelvin ?
|
|
|
|
temperature_t() : a.airtemp;
|
2013-02-14 17:44:18 +00:00
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
*/
|
2024-05-27 15:09:48 +00:00
|
|
|
static void fixup_dc_events(struct divecomputer &dc)
|
2011-09-03 20:19:26 +00:00
|
|
|
{
|
2024-05-25 06:16:57 +00:00
|
|
|
std::vector<int> to_delete;
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto [idx, event]: enumerated_range(dc.events)) {
|
2024-05-25 06:16:57 +00:00
|
|
|
if (!is_potentially_redundant(event))
|
|
|
|
continue;
|
|
|
|
for (int idx2 = idx - 1; idx2 > 0; --idx2) {
|
2024-05-27 15:09:48 +00:00
|
|
|
const auto &prev = dc.events[idx2];
|
2024-08-18 07:02:07 +00:00
|
|
|
if (event.time.seconds - prev.time.seconds > 60)
|
|
|
|
break;
|
|
|
|
if (range_contains(to_delete, idx2))
|
|
|
|
continue;
|
|
|
|
if (prev.name == event.name && prev.flags == event.flags) {
|
2024-05-25 06:16:57 +00:00
|
|
|
to_delete.push_back(idx);
|
2024-08-18 07:02:07 +00:00
|
|
|
break;
|
|
|
|
}
|
2013-02-09 00:35:39 +00:00
|
|
|
}
|
|
|
|
}
|
2024-05-25 06:16:57 +00:00
|
|
|
// Delete from back to not invalidate indexes
|
|
|
|
for (auto it = to_delete.rbegin(); it != to_delete.rend(); ++it)
|
2024-05-27 15:09:48 +00:00
|
|
|
dc.events.erase(dc.events.begin() + *it);
|
2013-02-09 00:35:39 +00:00
|
|
|
}
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
static int interpolate_depth(struct divecomputer &dc, int idx, int lastdepth, int lasttime, int now)
|
2015-10-25 03:02:08 +00:00
|
|
|
{
|
|
|
|
int nextdepth = lastdepth;
|
|
|
|
int nexttime = now;
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto it = dc.samples.begin() + idx; it != dc.samples.end(); ++it) {
|
2024-05-19 10:38:38 +00:00
|
|
|
if (it->depth.mm < 0)
|
2015-10-25 03:02:08 +00:00
|
|
|
continue;
|
2024-05-19 10:38:38 +00:00
|
|
|
nextdepth = it->depth.mm;
|
|
|
|
nexttime = it->time.seconds;
|
2015-10-25 03:02:08 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
return interpolate(lastdepth, nextdepth, now-lasttime, nexttime-lasttime);
|
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_dc_depths(struct dive &dive, struct divecomputer &dc)
|
2013-02-09 00:35:39 +00:00
|
|
|
{
|
2024-05-27 15:09:48 +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
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
for (const auto [idx, sample]: enumerated_range(dc.samples)) {
|
2024-05-19 10:38:38 +00:00
|
|
|
int time = sample.time.seconds;
|
|
|
|
int depth = sample.depth.mm;
|
2014-10-28 20:48:15 +00:00
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
if (depth < 0 && idx + 2 < static_cast<int>(dc.samples.size())) {
|
2024-05-19 10:38:38 +00:00
|
|
|
depth = interpolate_depth(dc, idx, lastdepth, lasttime, time);
|
|
|
|
sample.depth.mm = depth;
|
2015-10-25 03:02:08 +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
|
|
|
if (depth > SURFACE_THRESHOLD) {
|
|
|
|
if (depth > maxdepth)
|
|
|
|
maxdepth = depth;
|
|
|
|
}
|
|
|
|
|
|
|
|
lastdepth = depth;
|
|
|
|
lasttime = time;
|
2024-06-23 12:20:59 +00:00
|
|
|
if (sample.cns > dive.maxcns)
|
|
|
|
dive.maxcns = sample.cns;
|
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
|
|
|
}
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
update_depth(&dc.maxdepth, maxdepth);
|
2024-06-23 12:20:59 +00:00
|
|
|
if (!dive.is_logged() || !is_dc_planner(&dc))
|
|
|
|
if (maxdepth > dive.maxdepth.mm)
|
|
|
|
dive.maxdepth.mm = maxdepth;
|
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
|
|
|
}
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
static void fixup_dc_ndl(struct divecomputer &dc)
|
2017-11-05 16:32:38 +00:00
|
|
|
{
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto &sample: dc.samples) {
|
2024-05-19 10:38:38 +00:00
|
|
|
if (sample.ndl.seconds != 0)
|
2017-11-05 16:32:38 +00:00
|
|
|
break;
|
2024-05-19 10:38:38 +00:00
|
|
|
if (sample.ndl.seconds == 0)
|
|
|
|
sample.ndl.seconds = -1;
|
2017-11-05 16:32:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_dc_temp(struct dive &dive, 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
|
|
|
{
|
|
|
|
int mintemp = 0, lasttemp = 0;
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto &sample: dc.samples) {
|
2024-05-19 10:38:38 +00:00
|
|
|
int temp = sample.temperature.mkelvin;
|
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 (temp) {
|
|
|
|
/*
|
|
|
|
* If we have consecutive identical
|
|
|
|
* temperature readings, throw away
|
|
|
|
* the redundant ones.
|
|
|
|
*/
|
|
|
|
if (lasttemp == temp)
|
2024-05-19 10:38:38 +00:00
|
|
|
sample.temperature.mkelvin = 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
|
|
|
else
|
|
|
|
lasttemp = temp;
|
|
|
|
|
|
|
|
if (!mintemp || temp < mintemp)
|
|
|
|
mintemp = temp;
|
|
|
|
}
|
|
|
|
|
2024-05-19 10:38:38 +00:00
|
|
|
update_min_max_temperatures(dive, sample.temperature);
|
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
|
|
|
}
|
2024-05-27 15:09:48 +00:00
|
|
|
update_temperature(&dc.watertemp, mintemp);
|
|
|
|
update_min_max_temperatures(dive, dc.watertemp);
|
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
|
|
|
/* Remove redundant pressure information */
|
2024-05-27 15:09:48 +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
|
|
|
{
|
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
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto &sample: dc.samples) {
|
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;
|
|
|
|
|
2017-11-27 13:40:51 +00:00
|
|
|
for (j = 0; j < MAX_SENSORS; j++) {
|
2024-05-19 10:38:38 +00:00
|
|
|
int pressure = sample.pressure[j].mbar;
|
|
|
|
int index = sample.sensor[j];
|
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 (index == lastindex[j]) {
|
|
|
|
/* Remove duplicate redundant pressure information */
|
|
|
|
if (pressure == lastpressure[j])
|
2024-05-19 10:38:38 +00:00
|
|
|
sample.pressure[j].mbar = 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
|
|
|
}
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-03-19 15:22:51 +00:00
|
|
|
/* Do we need a sensor -> cylinder mapping? */
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_start_pressure(struct dive &dive, int idx, pressure_t p)
|
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
|
|
|
{
|
2024-06-23 12:20:59 +00:00
|
|
|
if (idx >= 0 && static_cast<size_t>(idx) < dive.cylinders.size()) {
|
|
|
|
cylinder_t &cyl = dive.cylinders[idx];
|
2024-05-28 19:31:11 +00:00
|
|
|
if (p.mbar && !cyl.sample_start.mbar)
|
|
|
|
cyl.sample_start = p;
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_end_pressure(struct dive &dive, int idx, pressure_t p)
|
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
|
|
|
{
|
2024-06-23 12:20:59 +00:00
|
|
|
if (idx >= 0 && static_cast<size_t>(idx) < dive.cylinders.size()) {
|
|
|
|
cylinder_t &cyl = dive.cylinders[idx];
|
2024-05-28 19:31:11 +00:00
|
|
|
if (p.mbar && !cyl.sample_end.mbar)
|
|
|
|
cyl.sample_end = p;
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
*/
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_dive_pressures(struct dive &dive, 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
|
|
|
{
|
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.. */
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto &sample: dc.samples) {
|
2024-05-19 10:38:38 +00:00
|
|
|
if (sample.depth.mm < SURFACE_THRESHOLD)
|
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
|
|
|
continue;
|
|
|
|
|
2024-05-19 10:38:38 +00:00
|
|
|
for (int 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 */
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto it = dc.samples.rbegin(); it != dc.samples.rend(); ++it) {
|
2024-05-19 10:38:38 +00:00
|
|
|
if (it->depth.mm < SURFACE_THRESHOLD)
|
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
|
|
|
continue;
|
|
|
|
|
2024-05-19 10:38:38 +00:00
|
|
|
for (int idx = 0; idx < MAX_SENSORS; idx++)
|
|
|
|
fixup_end_pressure(dive, it->sensor[idx], it->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
|
|
|
/*
|
|
|
|
* Match a gas change event against the cylinders we have
|
|
|
|
*/
|
2024-06-23 12:20:59 +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 */
|
2024-05-25 06:16:57 +00:00
|
|
|
if (gasmix_is_air(event.gas.mix))
|
|
|
|
event.gas.mix.o2.permille = 0;
|
2016-04-02 20:06:54 +00:00
|
|
|
|
|
|
|
/* Do we already have a cylinder index for this gasmix? */
|
2024-05-25 06:16:57 +00:00
|
|
|
if (event.gas.index >= 0)
|
2016-04-02 20:06:54 +00:00
|
|
|
return true;
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
index = find_best_gasmix_match(event.gas.mix, dive.cylinders);
|
|
|
|
if (index < 0 || static_cast<size_t>(index) >= dive.cylinders.size())
|
2016-04-02 20:06:54 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
/* Fix up the event to have the right information */
|
2024-05-25 06:16:57 +00:00
|
|
|
event.gas.index = index;
|
2024-06-23 12:20:59 +00:00
|
|
|
event.gas.mix = dive.cylinders[index].gasmix;
|
2016-04-02 20:06:54 +00:00
|
|
|
|
|
|
|
/* Convert to odd libdivecomputer format */
|
2024-05-25 06:16:57 +00:00
|
|
|
o2 = get_o2(event.gas.mix);
|
|
|
|
he = get_he(event.gas.mix);
|
2016-04-02 20:06:54 +00:00
|
|
|
|
|
|
|
o2 = (o2 + 5) / 10;
|
|
|
|
he = (he + 5) / 10;
|
|
|
|
value = o2 + (he << 16);
|
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
event.value = value;
|
2016-04-02 20:06:54 +00:00
|
|
|
if (he)
|
2024-05-25 06:16:57 +00:00
|
|
|
event.type = SAMPLE_EVENT_GASCHANGE2;
|
2016-04-02 20:06:54 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clean up event, return true if event is ok, false if it should be dropped as bogus */
|
2024-06-23 12:20:59 +00:00
|
|
|
static bool validate_event(struct dive &dive, struct event &event)
|
2016-04-02 20:06:54 +00:00
|
|
|
{
|
2024-05-25 18:12:10 +00:00
|
|
|
if (event.is_gaschange())
|
2016-04-05 14:40:32 +00:00
|
|
|
return validate_gaschange(dive, event);
|
2016-04-02 20:06:54 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_dc_gasswitch(struct dive &dive, struct divecomputer &dc)
|
2016-04-02 20:06:54 +00:00
|
|
|
{
|
2024-05-25 06:16:57 +00:00
|
|
|
// erase-remove idiom
|
2024-05-27 15:09:48 +00:00
|
|
|
auto &events = dc.events;
|
2024-05-25 06:16:57 +00:00
|
|
|
events.erase(std::remove_if(events.begin(), events.end(),
|
2024-06-23 12:20:59 +00:00
|
|
|
[&dive](auto &ev) { return !validate_event(dive, ev); }),
|
2024-05-25 06:16:57 +00:00
|
|
|
events.end());
|
2016-04-02 20:06:54 +00:00
|
|
|
}
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
static void fixup_no_o2sensors(struct divecomputer &dc)
|
2018-03-05 20:21:28 +00:00
|
|
|
{
|
|
|
|
// Its only relevant to look for sensor values on CCR and PSCR dives without any no_o2sensors recorded.
|
2024-05-27 15:09:48 +00:00
|
|
|
if (dc.no_o2sensors != 0 || !(dc.divemode == CCR || dc.divemode == PSCR))
|
2018-03-05 20:21:28 +00:00
|
|
|
return;
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
for (const auto &sample: dc.samples) {
|
2024-05-19 10:38:38 +00:00
|
|
|
int nsensor = 0;
|
2018-03-05 20:21:28 +00:00
|
|
|
|
|
|
|
// How many o2 sensors can we find in this sample?
|
2024-05-19 10:38:38 +00:00
|
|
|
for (int j = 0; j < MAX_O2_SENSORS; j++)
|
|
|
|
if (sample.o2sensor[j].mbar)
|
2024-01-20 23:35:44 +00:00
|
|
|
nsensor++;
|
2018-03-05 20:21:28 +00:00
|
|
|
|
|
|
|
// If we fond more than the previous found max, record it.
|
2024-05-27 15:09:48 +00:00
|
|
|
if (nsensor > dc.no_o2sensors)
|
|
|
|
dc.no_o2sensors = nsensor;
|
2018-03-05 20:21:28 +00:00
|
|
|
|
|
|
|
// Already found the maximum posible amount.
|
2024-01-20 23:35:44 +00:00
|
|
|
if (nsensor == MAX_O2_SENSORS)
|
2018-03-05 20:21:28 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_dc_sample_sensors(struct dive &dive, struct divecomputer &dc)
|
2021-07-18 10:51:47 +00:00
|
|
|
{
|
Fix the dc sensor index fixup
fixup_dc_sample_sensors() would make sure that any pressure sensor
indexes were in range of the cylinders by just clearing the pressure
data if the sensor index was larger than the number of cylinders in the
dive.
That certainly makes the sensor index data consistent, but at the cost
of just dropping the sensor data entirely.
Dirk had some cases of odd sensor data (probably because of an older
version of subsurface, but possibly due to removing cylinders manually
or because of oddities with the downloader for the Atomic Aquatics
Cobalt dive computer he used), and when re-saving the dive, the pressure
data would magically just get removed due to this.
So rewrite the sensor data fixup to strive very hard to avoid throwing
pressure sensor data away. The simplest way to do that is to just add
the required number of cylinders, and then people can fix up their dives
manually by remapping the sensor data.
This whole "we clear the pressure data" was at least partly hidden by
two things:
(1) in the git save format, we don't rewrite dives unless you've
changed the dive some way, so old dives stay around with old data
in the save until explicitly changed.
(2) if you had multiple dive computers, and one dive computer does not
have any pressure data but another one does, our profile will use
that "other" dive computer pressure data (because often times you
might have only one dive computer that is air integrated, but you
still want to see the tank pressure when you look at other dive
computers - or you have one dive computer give pressure data for
your deco bottle, and another for your travel gas etc).
So those two facts hid the reality that we had actually cleared the tank
sensor data for Dirk's dive with the Atomic Aquatics dive computer,
because we'd still see pressure data in the profile, and the git data
would still be the old one.
Until Dirk renumbered his dives, and the data was rewritten.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-09-12 20:38:22 +00:00
|
|
|
unsigned long sensor_mask = 0;
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto &sample: dc.samples) {
|
2021-07-18 10:51:47 +00:00
|
|
|
for (int j = 0; j < MAX_SENSORS; j++) {
|
2024-05-19 10:38:38 +00:00
|
|
|
int sensor = sample.sensor[j];
|
Fix the dc sensor index fixup
fixup_dc_sample_sensors() would make sure that any pressure sensor
indexes were in range of the cylinders by just clearing the pressure
data if the sensor index was larger than the number of cylinders in the
dive.
That certainly makes the sensor index data consistent, but at the cost
of just dropping the sensor data entirely.
Dirk had some cases of odd sensor data (probably because of an older
version of subsurface, but possibly due to removing cylinders manually
or because of oddities with the downloader for the Atomic Aquatics
Cobalt dive computer he used), and when re-saving the dive, the pressure
data would magically just get removed due to this.
So rewrite the sensor data fixup to strive very hard to avoid throwing
pressure sensor data away. The simplest way to do that is to just add
the required number of cylinders, and then people can fix up their dives
manually by remapping the sensor data.
This whole "we clear the pressure data" was at least partly hidden by
two things:
(1) in the git save format, we don't rewrite dives unless you've
changed the dive some way, so old dives stay around with old data
in the save until explicitly changed.
(2) if you had multiple dive computers, and one dive computer does not
have any pressure data but another one does, our profile will use
that "other" dive computer pressure data (because often times you
might have only one dive computer that is air integrated, but you
still want to see the tank pressure when you look at other dive
computers - or you have one dive computer give pressure data for
your deco bottle, and another for your travel gas etc).
So those two facts hid the reality that we had actually cleared the tank
sensor data for Dirk's dive with the Atomic Aquatics dive computer,
because we'd still see pressure data in the profile, and the git data
would still be the old one.
Until Dirk renumbered his dives, and the data was rewritten.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-09-12 20:38:22 +00:00
|
|
|
|
|
|
|
// No invalid sensor ID's, please
|
|
|
|
if (sensor < 0 || sensor > MAX_SENSORS) {
|
2024-05-19 10:38:38 +00:00
|
|
|
sample.sensor[j] = NO_SENSOR;
|
|
|
|
sample.pressure[j].mbar = 0;
|
Fix the dc sensor index fixup
fixup_dc_sample_sensors() would make sure that any pressure sensor
indexes were in range of the cylinders by just clearing the pressure
data if the sensor index was larger than the number of cylinders in the
dive.
That certainly makes the sensor index data consistent, but at the cost
of just dropping the sensor data entirely.
Dirk had some cases of odd sensor data (probably because of an older
version of subsurface, but possibly due to removing cylinders manually
or because of oddities with the downloader for the Atomic Aquatics
Cobalt dive computer he used), and when re-saving the dive, the pressure
data would magically just get removed due to this.
So rewrite the sensor data fixup to strive very hard to avoid throwing
pressure sensor data away. The simplest way to do that is to just add
the required number of cylinders, and then people can fix up their dives
manually by remapping the sensor data.
This whole "we clear the pressure data" was at least partly hidden by
two things:
(1) in the git save format, we don't rewrite dives unless you've
changed the dive some way, so old dives stay around with old data
in the save until explicitly changed.
(2) if you had multiple dive computers, and one dive computer does not
have any pressure data but another one does, our profile will use
that "other" dive computer pressure data (because often times you
might have only one dive computer that is air integrated, but you
still want to see the tank pressure when you look at other dive
computers - or you have one dive computer give pressure data for
your deco bottle, and another for your travel gas etc).
So those two facts hid the reality that we had actually cleared the tank
sensor data for Dirk's dive with the Atomic Aquatics dive computer,
because we'd still see pressure data in the profile, and the git data
would still be the old one.
Until Dirk renumbered his dives, and the data was rewritten.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-09-12 20:38:22 +00:00
|
|
|
continue;
|
2021-09-13 18:48:18 +00:00
|
|
|
}
|
Fix the dc sensor index fixup
fixup_dc_sample_sensors() would make sure that any pressure sensor
indexes were in range of the cylinders by just clearing the pressure
data if the sensor index was larger than the number of cylinders in the
dive.
That certainly makes the sensor index data consistent, but at the cost
of just dropping the sensor data entirely.
Dirk had some cases of odd sensor data (probably because of an older
version of subsurface, but possibly due to removing cylinders manually
or because of oddities with the downloader for the Atomic Aquatics
Cobalt dive computer he used), and when re-saving the dive, the pressure
data would magically just get removed due to this.
So rewrite the sensor data fixup to strive very hard to avoid throwing
pressure sensor data away. The simplest way to do that is to just add
the required number of cylinders, and then people can fix up their dives
manually by remapping the sensor data.
This whole "we clear the pressure data" was at least partly hidden by
two things:
(1) in the git save format, we don't rewrite dives unless you've
changed the dive some way, so old dives stay around with old data
in the save until explicitly changed.
(2) if you had multiple dive computers, and one dive computer does not
have any pressure data but another one does, our profile will use
that "other" dive computer pressure data (because often times you
might have only one dive computer that is air integrated, but you
still want to see the tank pressure when you look at other dive
computers - or you have one dive computer give pressure data for
your deco bottle, and another for your travel gas etc).
So those two facts hid the reality that we had actually cleared the tank
sensor data for Dirk's dive with the Atomic Aquatics dive computer,
because we'd still see pressure data in the profile, and the git data
would still be the old one.
Until Dirk renumbered his dives, and the data was rewritten.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-09-12 20:38:22 +00:00
|
|
|
|
|
|
|
// Don't bother tracking sensors with no data
|
2024-05-19 10:38:38 +00:00
|
|
|
if (!sample.pressure[j].mbar) {
|
|
|
|
sample.sensor[j] = NO_SENSOR;
|
Fix the dc sensor index fixup
fixup_dc_sample_sensors() would make sure that any pressure sensor
indexes were in range of the cylinders by just clearing the pressure
data if the sensor index was larger than the number of cylinders in the
dive.
That certainly makes the sensor index data consistent, but at the cost
of just dropping the sensor data entirely.
Dirk had some cases of odd sensor data (probably because of an older
version of subsurface, but possibly due to removing cylinders manually
or because of oddities with the downloader for the Atomic Aquatics
Cobalt dive computer he used), and when re-saving the dive, the pressure
data would magically just get removed due to this.
So rewrite the sensor data fixup to strive very hard to avoid throwing
pressure sensor data away. The simplest way to do that is to just add
the required number of cylinders, and then people can fix up their dives
manually by remapping the sensor data.
This whole "we clear the pressure data" was at least partly hidden by
two things:
(1) in the git save format, we don't rewrite dives unless you've
changed the dive some way, so old dives stay around with old data
in the save until explicitly changed.
(2) if you had multiple dive computers, and one dive computer does not
have any pressure data but another one does, our profile will use
that "other" dive computer pressure data (because often times you
might have only one dive computer that is air integrated, but you
still want to see the tank pressure when you look at other dive
computers - or you have one dive computer give pressure data for
your deco bottle, and another for your travel gas etc).
So those two facts hid the reality that we had actually cleared the tank
sensor data for Dirk's dive with the Atomic Aquatics dive computer,
because we'd still see pressure data in the profile, and the git data
would still be the old one.
Until Dirk renumbered his dives, and the data was rewritten.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-09-12 20:38:22 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remember the set of sensors we had
|
|
|
|
sensor_mask |= 1ul << sensor;
|
2021-07-18 10:51:47 +00:00
|
|
|
}
|
|
|
|
}
|
Fix the dc sensor index fixup
fixup_dc_sample_sensors() would make sure that any pressure sensor
indexes were in range of the cylinders by just clearing the pressure
data if the sensor index was larger than the number of cylinders in the
dive.
That certainly makes the sensor index data consistent, but at the cost
of just dropping the sensor data entirely.
Dirk had some cases of odd sensor data (probably because of an older
version of subsurface, but possibly due to removing cylinders manually
or because of oddities with the downloader for the Atomic Aquatics
Cobalt dive computer he used), and when re-saving the dive, the pressure
data would magically just get removed due to this.
So rewrite the sensor data fixup to strive very hard to avoid throwing
pressure sensor data away. The simplest way to do that is to just add
the required number of cylinders, and then people can fix up their dives
manually by remapping the sensor data.
This whole "we clear the pressure data" was at least partly hidden by
two things:
(1) in the git save format, we don't rewrite dives unless you've
changed the dive some way, so old dives stay around with old data
in the save until explicitly changed.
(2) if you had multiple dive computers, and one dive computer does not
have any pressure data but another one does, our profile will use
that "other" dive computer pressure data (because often times you
might have only one dive computer that is air integrated, but you
still want to see the tank pressure when you look at other dive
computers - or you have one dive computer give pressure data for
your deco bottle, and another for your travel gas etc).
So those two facts hid the reality that we had actually cleared the tank
sensor data for Dirk's dive with the Atomic Aquatics dive computer,
because we'd still see pressure data in the profile, and the git data
would still be the old one.
Until Dirk renumbered his dives, and the data was rewritten.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-09-12 20:38:22 +00:00
|
|
|
|
|
|
|
// Ignore the sensors we have cylinders for
|
2024-06-23 12:20:59 +00:00
|
|
|
sensor_mask >>= dive.cylinders.size();
|
Fix the dc sensor index fixup
fixup_dc_sample_sensors() would make sure that any pressure sensor
indexes were in range of the cylinders by just clearing the pressure
data if the sensor index was larger than the number of cylinders in the
dive.
That certainly makes the sensor index data consistent, but at the cost
of just dropping the sensor data entirely.
Dirk had some cases of odd sensor data (probably because of an older
version of subsurface, but possibly due to removing cylinders manually
or because of oddities with the downloader for the Atomic Aquatics
Cobalt dive computer he used), and when re-saving the dive, the pressure
data would magically just get removed due to this.
So rewrite the sensor data fixup to strive very hard to avoid throwing
pressure sensor data away. The simplest way to do that is to just add
the required number of cylinders, and then people can fix up their dives
manually by remapping the sensor data.
This whole "we clear the pressure data" was at least partly hidden by
two things:
(1) in the git save format, we don't rewrite dives unless you've
changed the dive some way, so old dives stay around with old data
in the save until explicitly changed.
(2) if you had multiple dive computers, and one dive computer does not
have any pressure data but another one does, our profile will use
that "other" dive computer pressure data (because often times you
might have only one dive computer that is air integrated, but you
still want to see the tank pressure when you look at other dive
computers - or you have one dive computer give pressure data for
your deco bottle, and another for your travel gas etc).
So those two facts hid the reality that we had actually cleared the tank
sensor data for Dirk's dive with the Atomic Aquatics dive computer,
because we'd still see pressure data in the profile, and the git data
would still be the old one.
Until Dirk renumbered his dives, and the data was rewritten.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-09-12 20:38:22 +00:00
|
|
|
|
|
|
|
// Do we need to add empty cylinders?
|
|
|
|
while (sensor_mask) {
|
2024-07-01 19:13:12 +00:00
|
|
|
dive.cylinders.emplace_back();
|
Fix the dc sensor index fixup
fixup_dc_sample_sensors() would make sure that any pressure sensor
indexes were in range of the cylinders by just clearing the pressure
data if the sensor index was larger than the number of cylinders in the
dive.
That certainly makes the sensor index data consistent, but at the cost
of just dropping the sensor data entirely.
Dirk had some cases of odd sensor data (probably because of an older
version of subsurface, but possibly due to removing cylinders manually
or because of oddities with the downloader for the Atomic Aquatics
Cobalt dive computer he used), and when re-saving the dive, the pressure
data would magically just get removed due to this.
So rewrite the sensor data fixup to strive very hard to avoid throwing
pressure sensor data away. The simplest way to do that is to just add
the required number of cylinders, and then people can fix up their dives
manually by remapping the sensor data.
This whole "we clear the pressure data" was at least partly hidden by
two things:
(1) in the git save format, we don't rewrite dives unless you've
changed the dive some way, so old dives stay around with old data
in the save until explicitly changed.
(2) if you had multiple dive computers, and one dive computer does not
have any pressure data but another one does, our profile will use
that "other" dive computer pressure data (because often times you
might have only one dive computer that is air integrated, but you
still want to see the tank pressure when you look at other dive
computers - or you have one dive computer give pressure data for
your deco bottle, and another for your travel gas etc).
So those two facts hid the reality that we had actually cleared the tank
sensor data for Dirk's dive with the Atomic Aquatics dive computer,
because we'd still see pressure data in the profile, and the git data
would still be the old one.
Until Dirk renumbered his dives, and the data was rewritten.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-09-12 20:38:22 +00:00
|
|
|
sensor_mask >>= 1;
|
|
|
|
}
|
2021-07-18 10:51:47 +00:00
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
static void fixup_dive_dc(struct dive &dive, 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
|
|
|
{
|
|
|
|
/* Fixup duration and mean depth */
|
|
|
|
fixup_dc_duration(dc);
|
|
|
|
|
|
|
|
/* Fix up sample depth data */
|
|
|
|
fixup_dc_depths(dive, dc);
|
|
|
|
|
2017-11-05 16:32:38 +00:00
|
|
|
/* Fix up first sample ndl data */
|
2017-12-08 14:19:13 +00:00
|
|
|
fixup_dc_ndl(dc);
|
2017-11-05 16:32:38 +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 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);
|
|
|
|
|
2021-07-18 10:51:47 +00:00
|
|
|
/* Fix up cylinder ids in pressure sensors */
|
Fix the dc sensor index fixup
fixup_dc_sample_sensors() would make sure that any pressure sensor
indexes were in range of the cylinders by just clearing the pressure
data if the sensor index was larger than the number of cylinders in the
dive.
That certainly makes the sensor index data consistent, but at the cost
of just dropping the sensor data entirely.
Dirk had some cases of odd sensor data (probably because of an older
version of subsurface, but possibly due to removing cylinders manually
or because of oddities with the downloader for the Atomic Aquatics
Cobalt dive computer he used), and when re-saving the dive, the pressure
data would magically just get removed due to this.
So rewrite the sensor data fixup to strive very hard to avoid throwing
pressure sensor data away. The simplest way to do that is to just add
the required number of cylinders, and then people can fix up their dives
manually by remapping the sensor data.
This whole "we clear the pressure data" was at least partly hidden by
two things:
(1) in the git save format, we don't rewrite dives unless you've
changed the dive some way, so old dives stay around with old data
in the save until explicitly changed.
(2) if you had multiple dive computers, and one dive computer does not
have any pressure data but another one does, our profile will use
that "other" dive computer pressure data (because often times you
might have only one dive computer that is air integrated, but you
still want to see the tank pressure when you look at other dive
computers - or you have one dive computer give pressure data for
your deco bottle, and another for your travel gas etc).
So those two facts hid the reality that we had actually cleared the tank
sensor data for Dirk's dive with the Atomic Aquatics dive computer,
because we'd still see pressure data in the profile, and the git data
would still be the old one.
Until Dirk renumbered his dives, and the data was rewritten.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-09-12 20:38:22 +00:00
|
|
|
fixup_dc_sample_sensors(dive, dc);
|
2021-07-18 10:51:47 +00:00
|
|
|
|
2022-09-12 20:07:57 +00:00
|
|
|
/* Fix up cylinder pressures based on DC info */
|
|
|
|
fixup_dive_pressures(dive, dc);
|
|
|
|
|
2013-02-09 00:35:39 +00:00
|
|
|
fixup_dc_events(dc);
|
2018-03-05 20:21:28 +00:00
|
|
|
|
|
|
|
/* Fixup CCR / PSCR dives with o2sensor values, but without no_o2sensors */
|
|
|
|
fixup_no_o2sensors(dc);
|
core: always create a fake profile if there are no samples
Before making the cylinder-table dynamic, dives always
had at least one cylinger. When such a dive is displayed,
the TabDiveInformation class calls per_cylinder_mean_depth().
If there are no samples, this function generates a "fake
profile" with fake_dc(). Thus, effectively dives always
had samples once the user was displaying them.
When the cylinder-table was made dynamic, dives without
cylinders were supported. This can notably happen, when
importing from CSV (this could actually be a bug).
per_cylinder_mean_depth() exits early in that case and
doesn't create a fake profile. This lead to crashes
of the profile-widget, which were fixed in 6b2e56e5131.
Non-sample dives were now shown with the Subsurface-logo.
To restore the previous behavior, genarate a fake profile
for sample-less dives in fixup_dive(), which is called
anytime a dive is loaded or imported. This seems to
have been the intention anyway and this worked only
"by chance". This will make a few fake_dc() calls obsolete,
but so be it.
Since fake profiles are now generated on loading,
the parse-tests need to be fixed to account for that.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2021-07-15 07:14:31 +00:00
|
|
|
|
|
|
|
/* If there are no samples, generate a fake profile based on depth and time */
|
2024-05-27 15:09:48 +00:00
|
|
|
if (dc.samples.empty())
|
|
|
|
fake_dc(&dc);
|
2013-02-09 00:35:39 +00:00
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
void dive::fixup_no_cylinder()
|
2013-02-09 00:35:39 +00:00
|
|
|
{
|
2024-06-23 12:20:59 +00:00
|
|
|
sanitize_cylinder_info(*this);
|
|
|
|
maxcns = cns;
|
2013-02-09 00:35:39 +00:00
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
update_min_max_temperatures(*this, watertemp);
|
2015-10-07 16:12:41 +00:00
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
for (auto &dc: dcs)
|
|
|
|
fixup_dive_dc(*this, dc);
|
2013-02-17 20:05:08 +00:00
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
fixup_water_salinity(*this);
|
|
|
|
if (!surface_pressure.mbar)
|
|
|
|
fixup_surface_pressure(*this);
|
|
|
|
fixup_meandepth(*this);
|
|
|
|
fixup_duration(*this);
|
|
|
|
fixup_watertemp(*this);
|
|
|
|
fixup_airtemp(*this);
|
|
|
|
for (auto &cyl: cylinders) {
|
2024-05-28 19:31:11 +00:00
|
|
|
add_cylinder_description(cyl.type);
|
|
|
|
if (same_rounded_pressure(cyl.sample_start, cyl.start))
|
|
|
|
cyl.start.mbar = 0;
|
|
|
|
if (same_rounded_pressure(cyl.sample_end, cyl.end))
|
|
|
|
cyl.end.mbar = 0;
|
2011-10-22 15:12:30 +00:00
|
|
|
}
|
|
|
|
|
2024-06-23 12:20:59 +00:00
|
|
|
for (auto &ws: weightsystems)
|
|
|
|
add_weightsystem_description(ws);
|
2011-09-03 20:19:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Don't pick a zero for MERGE_MIN() */
|
2024-06-24 18:47:02 +00:00
|
|
|
#define MERGE_MAX(res, a, b, n) res->n = std::max(a.n, b.n)
|
|
|
|
#define MERGE_MIN(res, a, b, n) res->n = (a.n) ? (b.n) ? std::min(a.n, b.n) : (a.n) : (b.n)
|
|
|
|
#define MERGE_TXT(res, a, b, n, sep) res->n = merge_text(a.n, b.n, sep)
|
|
|
|
#define MERGE_NONZERO(res, a, b, n) (res)->n = (a).n ? (a).n : (b).n
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2012-11-17 21:20:10 +00:00
|
|
|
/*
|
2024-05-25 06:50:20 +00:00
|
|
|
* This is like append_sample(), but if the distance from the last sample
|
2012-11-17 21:20:10 +00:00
|
|
|
* 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".
|
|
|
|
*/
|
2024-06-04 11:22:25 +00:00
|
|
|
static void merge_one_sample(const struct sample &sample, struct divecomputer &dc)
|
2012-11-17 21:20:10 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
if (!dc.samples.empty()) {
|
|
|
|
const struct sample &prev = dc.samples.back();
|
2024-05-19 10:38:38 +00:00
|
|
|
int last_time = prev.time.seconds;
|
|
|
|
int last_depth = prev.depth.mm;
|
2012-12-07 17:42:47 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Only do surface events if the samples are more than
|
|
|
|
* a minute apart, and shallower than 5m
|
|
|
|
*/
|
2024-05-25 06:50:20 +00:00
|
|
|
if (sample.time.seconds > last_time + 60 && last_depth < 5000) {
|
2024-02-27 15:31:44 +00:00
|
|
|
struct sample surface;
|
2018-09-18 08:55:29 +00:00
|
|
|
|
|
|
|
/* Init a few values from prev sample to avoid useless info in XML */
|
2024-05-19 10:38:38 +00:00
|
|
|
surface.bearing.degrees = prev.bearing.degrees;
|
|
|
|
surface.ndl.seconds = prev.ndl.seconds;
|
2024-05-25 06:50:20 +00:00
|
|
|
surface.time.seconds = last_time + 20;
|
2018-09-18 08:55:29 +00:00
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
append_sample(surface, &dc);
|
2024-05-25 06:50:20 +00:00
|
|
|
|
|
|
|
surface.time.seconds = sample.time.seconds - 20;
|
2024-06-04 11:22:25 +00:00
|
|
|
append_sample(surface, &dc);
|
2012-11-17 21:20:10 +00:00
|
|
|
}
|
|
|
|
}
|
2024-06-04 11:22:25 +00:00
|
|
|
append_sample(sample, &dc);
|
2012-11-17 21:20:10 +00:00
|
|
|
}
|
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
static void renumber_last_sample(struct divecomputer &dc, const int mapping[]);
|
2024-05-19 10:38:38 +00:00
|
|
|
static void sample_renumber(struct sample &s, const struct sample *next, const int mapping[]);
|
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'
|
|
|
|
*/
|
2024-06-04 11:22:25 +00:00
|
|
|
static void merge_samples(struct divecomputer &res,
|
|
|
|
const struct divecomputer &a, const struct divecomputer &b,
|
2018-08-13 02:47:07 +00:00
|
|
|
const int *cylinders_map_a, const int *cylinders_map_b,
|
|
|
|
int offset)
|
2011-09-03 20:19:26 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
auto as = a.samples.begin();
|
|
|
|
auto bs = b.samples.begin();
|
|
|
|
auto a_end = a.samples.end();
|
|
|
|
auto b_end = b.samples.end();
|
2011-09-03 20:19:26 +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
|
|
|
/*
|
|
|
|
* 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;
|
2024-05-19 10:38:38 +00:00
|
|
|
std::swap(as, bs);
|
|
|
|
std::swap(a_end, b_end);
|
|
|
|
std::swap(cylinders_map_a, cylinders_map_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
|
|
|
}
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
for (;;) {
|
2024-05-19 10:38:38 +00:00
|
|
|
int at = as != a_end ? as->time.seconds : -1;
|
|
|
|
int bt = bs != b_end ? bs->time.seconds + offset : -1;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
/* 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:
|
2024-05-25 06:50:20 +00:00
|
|
|
merge_one_sample(*as, res);
|
2018-08-13 02:47:07 +00:00
|
|
|
renumber_last_sample(res, cylinders_map_a);
|
2011-09-03 20:19:26 +00:00
|
|
|
as++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only samples from b? */
|
|
|
|
if (at < 0) {
|
2014-02-28 04:09:57 +00:00
|
|
|
add_sample_b:
|
2024-08-28 04:50:37 +00:00
|
|
|
struct sample sample = *bs;
|
|
|
|
sample.time.seconds += offset;
|
|
|
|
merge_one_sample(sample, res);
|
2018-08-13 02:47:07 +00:00
|
|
|
renumber_last_sample(res, cylinders_map_b);
|
2011-09-03 20:19:26 +00:00
|
|
|
bs++;
|
|
|
|
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 */
|
2024-05-19 10:38:38 +00:00
|
|
|
struct sample sample = *bs;
|
2024-08-28 04:50:37 +00:00
|
|
|
sample.time.seconds += offset;
|
2024-05-19 10:38:38 +00:00
|
|
|
sample_renumber(sample, nullptr, cylinders_map_b);
|
2011-09-03 20:19:26 +00:00
|
|
|
if (as->depth.mm)
|
|
|
|
sample.depth = as->depth;
|
|
|
|
if (as->temperature.mkelvin)
|
|
|
|
sample.temperature = as->temperature;
|
2024-05-19 10:38:38 +00:00
|
|
|
for (int j = 0; j < MAX_SENSORS; ++j) {
|
2018-08-13 02:47:07 +00:00
|
|
|
int sensor_id;
|
|
|
|
|
|
|
|
sensor_id = cylinders_map_a[as->sensor[j]];
|
|
|
|
if (sensor_id < 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (as->pressure[j].mbar)
|
|
|
|
sample.pressure[j] = as->pressure[j];
|
|
|
|
if (as->sensor[j])
|
|
|
|
sample.sensor[j] = sensor_id;
|
|
|
|
}
|
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
|
|
|
|
2024-05-25 06:50:20 +00:00
|
|
|
merge_one_sample(sample, res);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
as++;
|
|
|
|
bs++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-18 19:04:58 +00:00
|
|
|
static bool operator==(const struct extra_data &e1, const struct extra_data &e2)
|
2019-04-06 21:29:42 +00:00
|
|
|
{
|
2024-05-18 19:04:58 +00:00
|
|
|
return std::tie(e1.key, e1.value) == std::tie(e2.key, e2.value);
|
2019-04-06 21:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Merge extra_data.
|
|
|
|
*
|
|
|
|
* The extra data from 'a' has already been copied into 'res'. So
|
|
|
|
* we really should just copy over the data from 'b' too.
|
2024-05-18 19:04:58 +00:00
|
|
|
*
|
|
|
|
* This is not hugely efficient (with the whole "check this for
|
|
|
|
* every value you merge" it's O(n**2)) but it's not like we
|
|
|
|
* have very many extra_data entries per dive computer anyway.
|
2019-04-06 21:29:42 +00:00
|
|
|
*/
|
2024-06-04 11:22:25 +00:00
|
|
|
static void merge_extra_data(struct divecomputer &res,
|
|
|
|
const struct divecomputer &a, const struct divecomputer &b)
|
2019-04-06 21:29:42 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
for (auto &ed: b.extra_data) {
|
|
|
|
if (range_contains(a.extra_data, ed))
|
2019-04-06 21:29:42 +00:00
|
|
|
continue;
|
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
res.extra_data.push_back(ed);
|
2024-05-18 19:04:58 +00:00
|
|
|
}
|
2019-04-06 21:29:42 +00:00
|
|
|
}
|
|
|
|
|
2024-05-29 18:40:18 +00:00
|
|
|
static std::string merge_text(const std::string &a, const std::string &b, const char *sep)
|
2011-09-03 20:19:26 +00:00
|
|
|
{
|
2024-05-29 18:40:18 +00:00
|
|
|
if (a.empty())
|
|
|
|
return b;
|
|
|
|
if (b.empty())
|
|
|
|
return a;
|
|
|
|
if (a == b)
|
|
|
|
return a;
|
|
|
|
return a + sep + b;
|
2011-09-03 20:19:26 +00:00
|
|
|
}
|
|
|
|
|
2018-08-13 02:47:07 +00:00
|
|
|
#define SORT(a, b) \
|
|
|
|
if (a != b) \
|
|
|
|
return a < b ? -1 : 1
|
2024-05-25 06:16:57 +00:00
|
|
|
#define SORT_FIELD(a, b, field) SORT(a.field, b.field)
|
2011-09-23 03:28:04 +00:00
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
static int sort_event(const struct event &a, const struct event &b, int time_a, int time_b)
|
2011-09-23 03:28:04 +00:00
|
|
|
{
|
2018-08-13 02:47:07 +00:00
|
|
|
SORT(time_a, time_b);
|
|
|
|
SORT_FIELD(a, b, type);
|
|
|
|
SORT_FIELD(a, b, flags);
|
|
|
|
SORT_FIELD(a, b, value);
|
2024-05-25 06:16:57 +00:00
|
|
|
return a.name.compare(b.name);
|
2011-09-23 03:28:04 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 22:58:30 +00:00
|
|
|
static int same_gas(const struct event *a, const struct event *b)
|
2016-10-04 04:14:51 +00:00
|
|
|
{
|
2024-05-04 20:17:07 +00:00
|
|
|
if (a->type == b->type && a->flags == b->flags && a->value == b->value && a->name == b->name &&
|
2018-08-16 17:10:10 +00:00
|
|
|
same_gasmix(a->gas.mix, b->gas.mix)) {
|
2016-10-04 04:14:51 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
static void event_renumber(struct event &ev, const int mapping[]);
|
2024-06-04 11:22:25 +00:00
|
|
|
static void add_initial_gaschange(struct dive &dive, struct divecomputer &dc, int offset, int idx);
|
2018-08-13 02:47:07 +00:00
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
static void merge_events(struct dive &d, struct divecomputer &res,
|
|
|
|
const struct divecomputer &src1_in, const struct divecomputer &src2_in,
|
2018-08-13 02:47:07 +00:00
|
|
|
const int *cylinders_map1, const int *cylinders_map2,
|
|
|
|
int offset)
|
2011-09-23 03:28:04 +00:00
|
|
|
{
|
2018-08-13 02:47:07 +00:00
|
|
|
const 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 */
|
2024-06-04 11:22:25 +00:00
|
|
|
auto src1 = &src1_in;
|
|
|
|
auto src2 = &src2_in;
|
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
|
|
|
if (offset < 0) {
|
|
|
|
offset = -offset;
|
2024-06-04 11:22:25 +00:00
|
|
|
std::swap(src1, src2);
|
|
|
|
std::swap(cylinders_map1, cylinders_map2); // The pointers, not the contents are swapped.
|
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
|
|
|
}
|
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
auto a = src1->events.begin();
|
|
|
|
auto b = src2->events.begin();
|
2011-09-23 03:28:04 +00:00
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
while (a != src1->events.end() || b != src2->events.end()) {
|
|
|
|
int s = 0;
|
2018-08-13 02:47:07 +00:00
|
|
|
const struct event *pick;
|
|
|
|
const int *cylinders_map;
|
|
|
|
int event_offset;
|
2017-04-29 23:21:41 +00:00
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
if (b == src2->events.end())
|
2020-08-15 14:10:56 +00:00
|
|
|
goto pick_a;
|
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
if (a == src1->events.end())
|
2020-08-15 14:10:56 +00:00
|
|
|
goto pick_b;
|
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
s = sort_event(*a, *b, a->time.seconds, b->time.seconds + offset);
|
2016-10-04 04:14:51 +00:00
|
|
|
|
2020-08-15 14:10:56 +00:00
|
|
|
/* Identical events? Just skip one of them (we skip a) */
|
2017-04-29 23:21:41 +00:00
|
|
|
if (!s) {
|
2024-05-25 06:16:57 +00:00
|
|
|
++a;
|
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) {
|
2020-08-15 14:10:56 +00:00
|
|
|
pick_a:
|
2024-05-25 06:16:57 +00:00
|
|
|
pick = &*a;
|
|
|
|
++a;
|
2018-08-13 02:47:07 +00:00
|
|
|
event_offset = 0;
|
|
|
|
cylinders_map = cylinders_map1;
|
2017-04-29 23:21:41 +00:00
|
|
|
} else {
|
2020-08-15 14:10:56 +00:00
|
|
|
pick_b:
|
2024-05-25 06:16:57 +00:00
|
|
|
pick = &*b;
|
|
|
|
++b;
|
2018-08-13 02:47:07 +00:00
|
|
|
event_offset = offset;
|
|
|
|
cylinders_map = cylinders_map2;
|
2011-09-23 03:28:04 +00:00
|
|
|
}
|
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
|
|
|
|
*/
|
2024-05-25 18:12:10 +00:00
|
|
|
if (pick->is_gaschange()) {
|
2017-04-29 23:21:41 +00:00
|
|
|
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 */
|
2024-06-04 11:22:25 +00:00
|
|
|
res.events.push_back(*pick);
|
|
|
|
res.events.back().time.seconds += event_offset;
|
|
|
|
event_renumber(res.events.back(), cylinders_map);
|
2011-09-23 03:28:04 +00:00
|
|
|
}
|
2018-08-13 02:47:07 +00:00
|
|
|
|
|
|
|
/* If the initial cylinder of a divecomputer was remapped, add a gas change event to that cylinder */
|
|
|
|
if (cylinders_map1[0] > 0)
|
|
|
|
add_initial_gaschange(d, res, 0, cylinders_map1[0]);
|
|
|
|
if (cylinders_map2[0] > 0)
|
|
|
|
add_initial_gaschange(d, res, offset, cylinders_map2[0]);
|
2011-09-23 03:28:04 +00:00
|
|
|
}
|
|
|
|
|
2013-03-28 02:04:46 +00:00
|
|
|
/* Force an initial gaschange event to the (old) gas #0 */
|
2024-06-04 11:22:25 +00:00
|
|
|
static void add_initial_gaschange(struct dive &dive, struct divecomputer &dc, int offset, int idx)
|
2013-03-28 02:04:46 +00:00
|
|
|
{
|
2018-08-13 02:47:07 +00:00
|
|
|
/* if there is a gaschange event up to 30 sec after the initial event,
|
|
|
|
* refrain from adding the initial event */
|
2024-05-25 06:16:57 +00:00
|
|
|
event_loop loop("gaschange");
|
2024-06-04 11:22:25 +00:00
|
|
|
while(auto ev = loop.next(dc)) {
|
2018-08-13 02:47:07 +00:00
|
|
|
if (ev->time.seconds > offset + 30)
|
|
|
|
break;
|
|
|
|
else if (ev->time.seconds > offset)
|
|
|
|
return;
|
|
|
|
}
|
2013-03-28 02:04:46 +00:00
|
|
|
|
|
|
|
/* Old starting gas mix */
|
2024-06-04 11:22:25 +00:00
|
|
|
add_gas_switch_event(&dive, &dc, offset, idx);
|
2013-03-28 02:04:46 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 10:38:38 +00:00
|
|
|
static void sample_renumber(struct sample &s, const struct sample *prev, const int mapping[])
|
2013-03-28 02:04:46 +00:00
|
|
|
{
|
2024-05-19 10:38:38 +00:00
|
|
|
for (int j = 0; j < MAX_SENSORS; j++) {
|
2021-07-18 20:26:38 +00:00
|
|
|
int sensor = -1;
|
2013-03-28 02:04:46 +00:00
|
|
|
|
2024-05-19 10:38:38 +00:00
|
|
|
if (s.sensor[j] != NO_SENSOR)
|
|
|
|
sensor = mapping[s.sensor[j]];
|
2018-08-13 02:47:07 +00:00
|
|
|
if (sensor == -1) {
|
|
|
|
// Remove sensor and gas pressure info
|
2024-05-19 10:38:38 +00:00
|
|
|
if (!prev) {
|
|
|
|
s.sensor[j] = 0;
|
|
|
|
s.pressure[j].mbar = 0;
|
2017-11-29 09:16:00 +00:00
|
|
|
} else {
|
2024-05-19 10:38:38 +00:00
|
|
|
s.sensor[j] = prev->sensor[j];
|
|
|
|
s.pressure[j].mbar = prev->pressure[j].mbar;
|
2017-11-29 09:16:00 +00:00
|
|
|
}
|
2018-08-13 02:47:07 +00:00
|
|
|
} else {
|
2024-05-19 10:38:38 +00:00
|
|
|
s.sensor[j] = sensor;
|
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
|
|
|
}
|
2013-03-28 02:04:46 +00:00
|
|
|
}
|
2018-08-13 02:47:07 +00:00
|
|
|
}
|
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
static void renumber_last_sample(struct divecomputer &dc, const int mapping[])
|
2018-08-13 02:47:07 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
if (dc.samples.empty())
|
2018-08-13 02:47:07 +00:00
|
|
|
return;
|
2024-06-04 11:22:25 +00:00
|
|
|
sample *prev = dc.samples.size() > 1 ? &dc.samples[dc.samples.size() - 2] : nullptr;
|
|
|
|
sample_renumber(dc.samples.back(), prev, mapping);
|
2018-08-13 02:47:07 +00:00
|
|
|
}
|
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
static void event_renumber(struct event &ev, const int mapping[])
|
2018-08-13 02:47:07 +00:00
|
|
|
{
|
2024-05-25 18:12:10 +00:00
|
|
|
if (!ev.is_gaschange())
|
2018-08-13 02:47:07 +00:00
|
|
|
return;
|
2024-05-25 06:16:57 +00:00
|
|
|
if (ev.gas.index < 0)
|
2018-08-13 02:47:07 +00:00
|
|
|
return;
|
2024-05-25 06:16:57 +00:00
|
|
|
ev.gas.index = mapping[ev.gas.index];
|
2018-08-13 02:47:07 +00:00
|
|
|
}
|
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
static void dc_cylinder_renumber(struct dive &dive, struct divecomputer &dc, const int mapping[])
|
2018-08-13 02:47:07 +00:00
|
|
|
{
|
2020-03-11 10:30:51 +00:00
|
|
|
/* Remap or delete the sensor indices */
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto [i, sample]: enumerated_range(dc.samples))
|
|
|
|
sample_renumber(sample, i > 0 ? &dc.samples[i-1] : nullptr, mapping);
|
2014-08-17 18:26:21 +00:00
|
|
|
|
2020-03-11 10:30:51 +00:00
|
|
|
/* Remap the gas change indices */
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto &ev: dc.events)
|
2018-08-13 02:47:07 +00:00
|
|
|
event_renumber(ev, mapping);
|
|
|
|
|
|
|
|
/* If the initial cylinder of a dive was remapped, add a gas change event to that cylinder */
|
|
|
|
if (mapping[0] > 0)
|
2024-06-04 11:22:25 +00:00
|
|
|
add_initial_gaschange(dive, dc, 0, mapping[0]);
|
2013-03-28 02:04:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2020-03-11 10:30:51 +00:00
|
|
|
* If the cylinder indices change (due to merging dives or deleting
|
|
|
|
* cylinders in the middle), we need to change the indices in the
|
2013-03-28 02:04:46 +00:00
|
|
|
* 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
|
|
|
|
*/
|
2024-06-04 11:22:25 +00:00
|
|
|
void cylinder_renumber(struct dive &dive, int mapping[])
|
2013-03-28 02:04:46 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
for (auto &dc: dive.dcs)
|
2013-03-28 02:04:46 +00:00
|
|
|
dc_cylinder_renumber(dive, dc, mapping);
|
|
|
|
}
|
|
|
|
|
2024-05-28 19:31:11 +00:00
|
|
|
int same_gasmix_cylinder(const cylinder_t &cyl, int cylid, const struct dive *dive, bool check_unused)
|
2017-10-08 03:14:57 +00:00
|
|
|
{
|
2024-05-28 19:31:11 +00:00
|
|
|
struct gasmix mygas = cyl.gasmix;
|
|
|
|
for (auto [i, cyl]: enumerated_range(dive->cylinders)) {
|
2019-08-04 16:44:57 +00:00
|
|
|
if (i == cylid)
|
2017-10-08 03:14:57 +00:00
|
|
|
continue;
|
2024-05-28 19:31:11 +00:00
|
|
|
struct gasmix gas2 = cyl.gasmix;
|
2024-06-25 05:43:32 +00:00
|
|
|
if (gasmix_distance(mygas, gas2) == 0 && (dive->is_cylinder_used(i) || check_unused))
|
2017-10-08 03:14:57 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-08-23 17:18:43 +00:00
|
|
|
static int different_manual_pressures(const cylinder_t *a, const cylinder_t *b)
|
2017-02-08 23:00:04 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
*/
|
2024-06-04 11:22:25 +00:00
|
|
|
static int match_cylinder(const cylinder_t *cyl, const struct dive &dive, const bool try_match[])
|
2017-02-08 19:36:08 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
for (auto [i, target]: enumerated_range(dive.cylinders)) {
|
2020-06-28 21:26:26 +00:00
|
|
|
if (!try_match[i])
|
2017-02-08 19:36:08 +00:00
|
|
|
continue;
|
2020-06-28 21:26:26 +00:00
|
|
|
|
2024-05-28 19:31:11 +00:00
|
|
|
if (!same_gasmix(cyl->gasmix, target.gasmix))
|
2017-02-08 19:36:08 +00:00
|
|
|
continue;
|
2024-05-28 19:31:11 +00:00
|
|
|
if (cyl->cylinder_use != target.cylinder_use)
|
2017-02-08 23:00:04 +00:00
|
|
|
continue;
|
2024-05-28 19:31:11 +00:00
|
|
|
if (different_manual_pressures(cyl, &target))
|
2017-02-08 23:00:04 +00:00
|
|
|
continue;
|
2017-02-08 19:36:08 +00:00
|
|
|
|
2019-03-19 15:22:51 +00:00
|
|
|
/* open question: Should we check sizes too? */
|
2017-02-08 19:36:08 +00:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
core: improve merging of cylinders pressures
When merging cylinders pressures derived from samples were taken
as maximum of the start and minimum of the end pressure, which
makes sense, since we believe that this is the same cylinder.
However, for manually entered pressures, this was not done.
Moreover, when one dive had manual pressures and the other only
pressure from samples, the manual pressure was taken. However,
that could have been the wrong one, for example if the end
pressure was manually set for the cylinder of the first part of
the dive, but not the last.
Therefore, improve merging of manuall set pressures in two ways:
1) use maximum/minimum for start/end pressure
2) if the pressure of one cylinder was manually set, but not for
the other, complete with the sample pressure (if that exists).
Fixes #2884.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2020-10-03 15:03:28 +00:00
|
|
|
/*
|
|
|
|
* Function used to merge manually set start or end pressures. This
|
|
|
|
* is used to merge cylinders when merging dives. We store up to two
|
|
|
|
* values for start _and_ end pressures: one derived from samples and
|
|
|
|
* one entered manually, whereby the latter takes precedence. It may
|
|
|
|
* happen that the user merges two dives where one has a manual,
|
|
|
|
* the other only a sample-derived pressure. In such a case we want to
|
|
|
|
* supplement the non-existing manual value by a sample derived one.
|
|
|
|
* Otherwise, the merged dive would end up with incomplete pressure
|
|
|
|
* information.
|
|
|
|
* The last argument to the function specifies whether the larger
|
|
|
|
* or smaller value of the two dives should be returned. Obviously,
|
|
|
|
* for the starting pressure we want the larger and for the ending
|
|
|
|
* pressure the smaller value.
|
|
|
|
*/
|
|
|
|
static pressure_t merge_pressures(pressure_t a, pressure_t sample_a, pressure_t b, pressure_t sample_b, bool take_min)
|
|
|
|
{
|
|
|
|
if (!a.mbar && !b.mbar)
|
|
|
|
return a;
|
|
|
|
if (!a.mbar)
|
|
|
|
a = sample_a;
|
|
|
|
if (!b.mbar)
|
|
|
|
b = sample_b;
|
|
|
|
if (!a.mbar)
|
|
|
|
a = b;
|
|
|
|
if (!b.mbar)
|
|
|
|
b = a;
|
2023-08-07 07:34:55 +00:00
|
|
|
if (take_min)
|
|
|
|
return a.mbar < b.mbar? a : b;
|
|
|
|
return a.mbar > b.mbar? a : b;
|
core: improve merging of cylinders pressures
When merging cylinders pressures derived from samples were taken
as maximum of the start and minimum of the end pressure, which
makes sense, since we believe that this is the same cylinder.
However, for manually entered pressures, this was not done.
Moreover, when one dive had manual pressures and the other only
pressure from samples, the manual pressure was taken. However,
that could have been the wrong one, for example if the end
pressure was manually set for the cylinder of the first part of
the dive, but not the last.
Therefore, improve merging of manuall set pressures in two ways:
1) use maximum/minimum for start/end pressure
2) if the pressure of one cylinder was manually set, but not for
the other, complete with the sample pressure (if that exists).
Fixes #2884.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2020-10-03 15:03:28 +00:00
|
|
|
}
|
|
|
|
|
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'.
|
|
|
|
*/
|
2020-06-28 21:26:26 +00:00
|
|
|
static void merge_one_cylinder(cylinder_t *a, const 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;
|
2024-05-28 19:31:11 +00:00
|
|
|
if (a->type.description.empty())
|
|
|
|
a->type.description = b->type.description;
|
core: improve merging of cylinders pressures
When merging cylinders pressures derived from samples were taken
as maximum of the start and minimum of the end pressure, which
makes sense, since we believe that this is the same cylinder.
However, for manually entered pressures, this was not done.
Moreover, when one dive had manual pressures and the other only
pressure from samples, the manual pressure was taken. However,
that could have been the wrong one, for example if the end
pressure was manually set for the cylinder of the first part of
the dive, but not the last.
Therefore, improve merging of manuall set pressures in two ways:
1) use maximum/minimum for start/end pressure
2) if the pressure of one cylinder was manually set, but not for
the other, complete with the sample pressure (if that exists).
Fixes #2884.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2020-10-03 15:03:28 +00:00
|
|
|
|
|
|
|
/* If either cylinder has manually entered pressures, try to merge them.
|
|
|
|
* Use pressures from divecomputer samples if only one cylinder has such a value.
|
2020-10-03 22:07:59 +00:00
|
|
|
* Yes, this is an actual use case we encountered.
|
|
|
|
* Note that we don't merge the sample-derived pressure values, as this is
|
|
|
|
* perfomed after merging in fixup_dive() */
|
core: improve merging of cylinders pressures
When merging cylinders pressures derived from samples were taken
as maximum of the start and minimum of the end pressure, which
makes sense, since we believe that this is the same cylinder.
However, for manually entered pressures, this was not done.
Moreover, when one dive had manual pressures and the other only
pressure from samples, the manual pressure was taken. However,
that could have been the wrong one, for example if the end
pressure was manually set for the cylinder of the first part of
the dive, but not the last.
Therefore, improve merging of manuall set pressures in two ways:
1) use maximum/minimum for start/end pressure
2) if the pressure of one cylinder was manually set, but not for
the other, complete with the sample pressure (if that exists).
Fixes #2884.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
2020-10-03 15:03:28 +00:00
|
|
|
a->start = merge_pressures(a->start, a->sample_start, b->start, b->sample_start, false);
|
|
|
|
a->end = merge_pressures(a->end, a->sample_end, b->end, b->sample_end, true);
|
2017-09-30 12:21:26 +00:00
|
|
|
|
2020-06-28 21:26:26 +00:00
|
|
|
/* Really? */
|
|
|
|
a->gas_used.mliter += b->gas_used.mliter;
|
|
|
|
a->deco_gas_used.mliter += b->deco_gas_used.mliter;
|
|
|
|
a->bestmix_o2 = a->bestmix_o2 && b->bestmix_o2;
|
|
|
|
a->bestmix_he = a->bestmix_he && b->bestmix_he;
|
|
|
|
}
|
2018-08-13 02:47:07 +00:00
|
|
|
|
2024-05-28 19:31:11 +00:00
|
|
|
static bool cylinder_has_data(const cylinder_t &cyl)
|
2020-06-28 21:26:26 +00:00
|
|
|
{
|
2024-05-28 19:31:11 +00:00
|
|
|
return !cyl.type.size.mliter &&
|
|
|
|
!cyl.type.workingpressure.mbar &&
|
|
|
|
cyl.type.description.empty() &&
|
|
|
|
!cyl.gasmix.o2.permille &&
|
|
|
|
!cyl.gasmix.he.permille &&
|
|
|
|
!cyl.start.mbar &&
|
|
|
|
!cyl.end.mbar &&
|
|
|
|
!cyl.sample_start.mbar &&
|
|
|
|
!cyl.sample_end.mbar &&
|
|
|
|
!cyl.gas_used.mliter &&
|
|
|
|
!cyl.deco_gas_used.mliter;
|
2020-06-28 21:26:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool cylinder_in_use(const struct dive *dive, int idx)
|
|
|
|
{
|
2024-05-28 19:31:11 +00:00
|
|
|
if (idx < 0 || static_cast<size_t>(idx) >= dive->cylinders.size())
|
2020-06-28 21:26:26 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
/* This tests for gaschange events or pressure changes */
|
2024-06-25 05:43:32 +00:00
|
|
|
if (dive->is_cylinder_used(idx) || prefs.include_unused_tanks)
|
2020-06-28 21:26:26 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
/* This tests for typenames or gas contents */
|
2024-05-28 19:31:11 +00:00
|
|
|
return cylinder_has_data(dive->cylinders[idx]);
|
2017-02-08 23:00:04 +00:00
|
|
|
}
|
|
|
|
|
2024-05-15 05:23:39 +00:00
|
|
|
bool is_cylinder_use_appropriate(const struct divecomputer &dc, const cylinder_t &cyl, bool allowNonUsable)
|
|
|
|
{
|
|
|
|
switch (cyl.cylinder_use) {
|
|
|
|
case OC:
|
|
|
|
if (dc.divemode == FREEDIVE)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
break;
|
|
|
|
case OXYGEN:
|
|
|
|
if (!allowNonUsable)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
case DILUENT:
|
|
|
|
if (dc.divemode != CCR)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
break;
|
|
|
|
case NOT_USED:
|
|
|
|
if (!allowNonUsable)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
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.
|
2018-08-13 02:47:07 +00:00
|
|
|
*
|
2019-08-04 16:44:57 +00:00
|
|
|
* For each dive, a cylinder-renumbering table is returned.
|
2013-03-28 02:04:46 +00:00
|
|
|
*/
|
2024-06-04 11:22:25 +00:00
|
|
|
static void merge_cylinders(struct dive &res, const struct dive &a, const struct dive &b,
|
2018-08-13 02:47:07 +00:00
|
|
|
int mapping_a[], int mapping_b[])
|
2013-03-28 02:04:46 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
size_t max_cylinders = a.cylinders.size() + b.cylinders.size();
|
2024-02-27 12:24:36 +00:00
|
|
|
auto used_in_a = std::make_unique<bool[]>(max_cylinders);
|
|
|
|
auto used_in_b = std::make_unique<bool[]>(max_cylinders);
|
|
|
|
auto try_to_match = std::make_unique<bool[]>(max_cylinders);
|
|
|
|
std::fill(try_to_match.get(), try_to_match.get() + max_cylinders, false);
|
2017-02-08 19:36:08 +00:00
|
|
|
|
2018-08-13 02:47:07 +00:00
|
|
|
/* First, clear all cylinders in destination */
|
2024-06-04 11:22:25 +00:00
|
|
|
res.cylinders.clear();
|
2018-08-13 02:47:07 +00:00
|
|
|
|
2020-06-28 21:26:26 +00:00
|
|
|
/* Clear all cylinder mappings */
|
2024-06-04 11:22:25 +00:00
|
|
|
std::fill(mapping_a, mapping_a + a.cylinders.size(), -1);
|
|
|
|
std::fill(mapping_b, mapping_b + b.cylinders.size(), -1);
|
2020-06-28 21:26:26 +00:00
|
|
|
|
|
|
|
/* Calculate usage map of cylinders, clear matching map */
|
2024-05-28 19:31:11 +00:00
|
|
|
for (size_t i = 0; i < max_cylinders; i++) {
|
2024-06-04 11:22:25 +00:00
|
|
|
used_in_a[i] = cylinder_in_use(&a, i);
|
|
|
|
used_in_b[i] = cylinder_in_use(&b, i);
|
2019-08-04 16:44:57 +00:00
|
|
|
}
|
|
|
|
|
2020-06-28 21:26:26 +00:00
|
|
|
/*
|
|
|
|
* For each cylinder in 'a' that is used, copy it to 'res'.
|
|
|
|
* These are also potential matches for 'b' to use.
|
|
|
|
*/
|
2024-05-28 19:31:11 +00:00
|
|
|
for (size_t i = 0; i < max_cylinders; i++) {
|
2024-06-04 11:22:25 +00:00
|
|
|
size_t res_nr = res.cylinders.size();
|
2020-06-28 21:26:26 +00:00
|
|
|
if (!used_in_a[i])
|
|
|
|
continue;
|
2024-05-28 19:31:11 +00:00
|
|
|
mapping_a[i] = static_cast<int>(res_nr);
|
2020-06-28 21:26:26 +00:00
|
|
|
try_to_match[res_nr] = true;
|
2024-06-04 11:22:25 +00:00
|
|
|
res.cylinders.push_back(a.cylinders[i]);
|
2017-02-08 19:36:08 +00:00
|
|
|
}
|
|
|
|
|
2020-06-28 21:26:26 +00:00
|
|
|
/*
|
|
|
|
* For each cylinder in 'b' that is used, try to match it
|
|
|
|
* with an existing cylinder in 'res' from 'a'
|
|
|
|
*/
|
2024-06-04 11:22:25 +00:00
|
|
|
for (size_t i = 0; i < b.cylinders.size(); i++) {
|
2017-02-08 19:36:08 +00:00
|
|
|
int j;
|
2013-03-28 02:04:46 +00:00
|
|
|
|
2019-07-25 08:28:58 +00:00
|
|
|
if (!used_in_b[i])
|
2017-02-08 19:36:08 +00:00
|
|
|
continue;
|
|
|
|
|
2024-06-25 05:43:32 +00:00
|
|
|
j = match_cylinder(b.get_cylinder(i), res, try_to_match.get());
|
2017-02-08 19:36:08 +00:00
|
|
|
|
2020-06-28 21:26:26 +00:00
|
|
|
/* No match? Add it to the result */
|
|
|
|
if (j < 0) {
|
2024-06-04 11:22:25 +00:00
|
|
|
size_t res_nr = res.cylinders.size();
|
2024-05-28 19:31:11 +00:00
|
|
|
mapping_b[i] = static_cast<int>(res_nr);
|
2024-06-04 11:22:25 +00:00
|
|
|
res.cylinders.push_back(b.cylinders[i]);
|
2020-06-28 21:26:26 +00:00
|
|
|
continue;
|
2019-08-04 16:44:57 +00:00
|
|
|
}
|
2020-06-28 21:26:26 +00:00
|
|
|
|
|
|
|
/* Otherwise, merge the result to the one we found */
|
|
|
|
mapping_b[i] = j;
|
2024-06-25 05:43:32 +00:00
|
|
|
merge_one_cylinder(res.get_cylinder(j), b.get_cylinder(i));
|
2020-06-28 21:26:26 +00:00
|
|
|
|
|
|
|
/* Don't match the same target more than once */
|
|
|
|
try_to_match[j] = false;
|
2017-02-02 14:31:52 +00:00
|
|
|
}
|
2013-03-28 02:04:46 +00:00
|
|
|
}
|
|
|
|
|
2019-06-26 15:21:03 +00:00
|
|
|
/* Check whether a weightsystem table contains a given weightsystem */
|
2024-05-29 05:03:03 +00:00
|
|
|
static bool has_weightsystem(const weightsystem_table &t, const weightsystem_t &w)
|
2019-06-26 15:21:03 +00:00
|
|
|
{
|
2024-06-30 20:12:25 +00:00
|
|
|
return any_of(t.begin(), t.end(), [&w] (auto &w2) { return w == w2; });
|
2019-06-26 15:21:03 +00:00
|
|
|
}
|
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
static void merge_equipment(struct dive &res, const struct dive &a, const struct dive &b)
|
2012-10-29 18:27:14 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
for (auto &ws: a.weightsystems) {
|
|
|
|
if (!has_weightsystem(res.weightsystems, ws))
|
|
|
|
res.weightsystems.push_back(ws);
|
2019-06-26 15:21:03 +00:00
|
|
|
}
|
2024-06-04 11:22:25 +00:00
|
|
|
for (auto &ws: b.weightsystems) {
|
|
|
|
if (!has_weightsystem(res.weightsystems, ws))
|
|
|
|
res.weightsystems.push_back(ws);
|
2019-06-26 15:21:03 +00:00
|
|
|
}
|
2012-10-29 18:27:14 +00:00
|
|
|
}
|
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
static void merge_temperatures(struct dive &res, const struct dive &a, const struct dive &b)
|
2013-02-14 17:44:18 +00:00
|
|
|
{
|
2018-08-13 02:47:07 +00:00
|
|
|
temperature_t airtemp_a = un_fixup_airtemp(a);
|
|
|
|
temperature_t airtemp_b = un_fixup_airtemp(b);
|
2024-06-04 11:22:25 +00:00
|
|
|
res.airtemp = airtemp_a.mkelvin ? airtemp_a : airtemp_b;
|
2024-06-24 18:47:02 +00:00
|
|
|
MERGE_NONZERO(&res, a, b, watertemp.mkelvin);
|
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'.
|
|
|
|
*
|
2024-05-19 10:38:38 +00:00
|
|
|
* If 's' and 'a' are at the same time, offset is 0.
|
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
|
|
|
*/
|
2024-05-19 10:38:38 +00:00
|
|
|
static int compare_sample(const struct sample &s, const struct sample &a, const struct sample &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
|
|
|
{
|
2024-05-19 10:38:38 +00:00
|
|
|
unsigned int depth = a.depth.mm;
|
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 diff;
|
|
|
|
|
|
|
|
if (offset) {
|
2024-05-19 10:38:38 +00:00
|
|
|
unsigned int interval = b.time.seconds - a.time.seconds;
|
|
|
|
unsigned int depth_a = a.depth.mm;
|
|
|
|
unsigned int depth_b = b.depth.mm;
|
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
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2024-05-19 10:38:38 +00:00
|
|
|
diff = s.depth.mm - depth;
|
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
|
|
|
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
|
|
|
{
|
2024-05-19 10:38:38 +00:00
|
|
|
if (a->samples.empty() || b->samples.empty())
|
|
|
|
return;
|
|
|
|
|
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
|
|
|
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.
|
|
|
|
*/
|
2024-05-19 10:38:38 +00:00
|
|
|
auto as = a->samples.begin() + 1;
|
|
|
|
auto bs = a->samples.begin() + 1;
|
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
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
/* If we run out of samples, punt */
|
2024-05-19 10:38:38 +00:00
|
|
|
if (as == a->samples.end())
|
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
|
|
|
return INT_MAX;
|
2024-05-19 10:38:38 +00:00
|
|
|
if (bs == b->samples.end())
|
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
|
|
|
return INT_MAX;
|
|
|
|
|
2024-05-19 10:38:38 +00:00
|
|
|
int at = as->time.seconds;
|
|
|
|
int bt = bs->time.seconds + 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
|
|
|
|
|
|
|
/* b hasn't started yet? Ignore it */
|
|
|
|
if (bt < 0) {
|
2024-05-19 10:38:38 +00:00
|
|
|
++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
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-05-19 10:38:38 +00:00
|
|
|
int 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
|
|
|
if (at < bt) {
|
2024-05-19 10:38:38 +00:00
|
|
|
diff = compare_sample(*as, *std::prev(bs), *bs, bt - at);
|
|
|
|
++as;
|
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
|
|
|
} else if (at > bt) {
|
2024-05-19 10:38:38 +00:00
|
|
|
diff = compare_sample(*bs, *std::prev(as), *as, at - bt);
|
|
|
|
++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
|
|
|
} else {
|
2024-05-19 10:38:38 +00:00
|
|
|
diff = compare_sample(*as, *bs, *bs, 0);
|
|
|
|
++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
|
|
|
}
|
|
|
|
|
|
|
|
/* 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
|
|
|
{
|
|
|
|
/* No samples? Merge at any time (0 offset) */
|
2024-05-19 10:38:38 +00:00
|
|
|
if (a->samples.empty())
|
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
|
|
|
return 0;
|
2024-05-19 10:38:38 +00:00
|
|
|
if (b->samples.empty())
|
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
|
|
|
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.
|
|
|
|
*/
|
2024-05-19 10:38:38 +00:00
|
|
|
int best = 0;
|
|
|
|
unsigned long max = sample_difference(a, b, 0);
|
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
|
|
|
if (!max)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Otherwise, look if we can find anything better within
|
|
|
|
* a thirty second window..
|
|
|
|
*/
|
2024-05-19 10:38:38 +00:00
|
|
|
for (int offset = -30; offset <= 30; 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
|
|
|
unsigned long diff;
|
|
|
|
|
2024-05-19 10:38:38 +00:00
|
|
|
int diff = sample_difference(a, b, 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
|
|
|
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 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"
|
|
|
|
*/
|
2024-05-27 15:09:48 +00:00
|
|
|
static int match_dc_dive(const struct dive &a, const struct dive &b)
|
2012-12-16 18:55:58 +00:00
|
|
|
{
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto &dc1: a.dcs) {
|
|
|
|
for (auto &dc2: b.dcs) {
|
|
|
|
int match = match_one_dc(dc1, dc2);
|
2012-12-16 18:55:58 +00:00
|
|
|
if (match)
|
|
|
|
return match;
|
2024-05-27 15:09:48 +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
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2024-06-24 18:47:02 +00:00
|
|
|
bool dive::likely_same(const struct dive &b) const
|
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 */
|
2024-06-24 18:47:02 +00:00
|
|
|
if (is_dc_manually_added_dive(&dcs[0]) ||
|
2024-06-04 11:22:25 +00:00
|
|
|
is_dc_manually_added_dive(&b.dcs[0]))
|
2015-09-30 11:45:55 +00:00
|
|
|
return 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
|
|
|
/*
|
|
|
|
* Do some basic sanity testing of the values we
|
|
|
|
* have filled in during 'fixup_dive()'
|
|
|
|
*/
|
2024-06-24 18:47:02 +00:00
|
|
|
if (!similar(maxdepth.mm, b.maxdepth.mm, 1000) ||
|
|
|
|
(meandepth.mm && b.meandepth.mm && !similar(meandepth.mm, b.meandepth.mm, 1000)) ||
|
|
|
|
!duration.seconds || !b.duration.seconds ||
|
|
|
|
!similar(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 */
|
2024-06-24 18:47:02 +00:00
|
|
|
if (match_dc_dive(*this, b))
|
2024-06-04 11:22:25 +00:00
|
|
|
return true;
|
2012-12-16 18:55:58 +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
|
|
|
/*
|
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
|
|
|
*/
|
2024-06-24 18:47:02 +00:00
|
|
|
int fuzz = std::max(duration.seconds, b.duration.seconds) / 2;
|
2024-06-04 11:22:25 +00:00
|
|
|
fuzz = std::max(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
|
|
|
|
2024-06-24 18:47:02 +00:00
|
|
|
return (when <= b.when + fuzz) && (when >= b.when - fuzz);
|
2012-11-25 04:29:14 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 10:38:38 +00:00
|
|
|
static bool operator==(const sample &a, const sample &b)
|
2012-11-25 04:29:14 +00:00
|
|
|
{
|
2024-05-19 10:38:38 +00:00
|
|
|
if (a.time.seconds != b.time.seconds)
|
|
|
|
return false;
|
|
|
|
if (a.depth.mm != b.depth.mm)
|
|
|
|
return false;
|
|
|
|
if (a.temperature.mkelvin != b.temperature.mkelvin)
|
|
|
|
return false;
|
|
|
|
if (a.pressure[0].mbar != b.pressure[0].mbar)
|
|
|
|
return false;
|
|
|
|
return a.sensor[0] == b.sensor[0];
|
2012-11-25 04:29:14 +00:00
|
|
|
}
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
static int same_dc(const struct divecomputer &a, const struct divecomputer &b)
|
2012-11-25 04:29:14 +00:00
|
|
|
{
|
|
|
|
int i;
|
2012-12-16 20:33:37 +00:00
|
|
|
i = match_one_dc(a, b);
|
|
|
|
if (i)
|
|
|
|
return i > 0;
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
if (a.when && b.when && a.when != b.when)
|
2012-11-25 04:29:14 +00:00
|
|
|
return 0;
|
2024-05-27 15:09:48 +00:00
|
|
|
if (a.samples != b.samples)
|
2012-11-25 04:29:14 +00:00
|
|
|
return 0;
|
2024-05-27 15:09:48 +00:00
|
|
|
return a.events == b.events;
|
2012-11-25 04:29:14 +00:00
|
|
|
}
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
static int might_be_same_device(const struct divecomputer &a, const struct divecomputer &b)
|
2013-01-23 00:57:07 +00:00
|
|
|
{
|
|
|
|
/* No dive computer model? That matches anything */
|
2024-05-27 15:09:48 +00:00
|
|
|
if (a.model.empty() || b.model.empty())
|
2013-01-23 00:57:07 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
/* Otherwise at least the model names have to match */
|
2024-05-27 15:09:48 +00:00
|
|
|
if (strcasecmp(a.model.c_str(), b.model.c_str()))
|
2013-01-23 00:57:07 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* No device ID? Match */
|
2024-05-27 15:09:48 +00:00
|
|
|
if (!a.deviceid || !b.deviceid)
|
2013-01-23 00:57:07 +00:00
|
|
|
return 1;
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
return a.deviceid == b.deviceid;
|
2013-01-23 00:57:07 +00:00
|
|
|
}
|
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
static void remove_redundant_dc(struct dive &d, bool prefer_downloaded)
|
2012-11-25 04:29:14 +00:00
|
|
|
{
|
2024-05-27 15:09:48 +00:00
|
|
|
// Note: since the vector doesn't grow and we only erase
|
|
|
|
// elements after the iterator, this is fine.
|
2024-06-04 11:22:25 +00:00
|
|
|
for (auto it = d.dcs.begin(); it != d.dcs.end(); ++it) {
|
2024-05-27 15:09:48 +00:00
|
|
|
// Remove all following DCs that compare as equal.
|
|
|
|
// Use the (infamous) erase-remove idiom.
|
2024-06-04 11:22:25 +00:00
|
|
|
auto it2 = std::remove_if(std::next(it), d.dcs.end(),
|
2024-05-27 15:09:48 +00:00
|
|
|
[d, prefer_downloaded, &it] (const divecomputer &dc) {
|
|
|
|
return same_dc(*it, dc) ||
|
|
|
|
(prefer_downloaded && might_be_same_device(*it, dc));
|
|
|
|
});
|
2024-06-04 11:22:25 +00:00
|
|
|
d.dcs.erase(it2, d.dcs.end());
|
2012-11-25 04:29:14 +00:00
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
prefer_downloaded = false;
|
|
|
|
}
|
2012-11-11 06:20:05 +00:00
|
|
|
}
|
|
|
|
|
2024-06-04 11:22:25 +00:00
|
|
|
static const struct divecomputer *find_matching_computer(const struct divecomputer &match, const struct dive &d)
|
2012-11-25 20:39:08 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
for (const auto &dc: d.dcs) {
|
2024-05-27 15:09:48 +00:00
|
|
|
if (might_be_same_device(match, dc))
|
|
|
|
return &dc;
|
2012-11-25 20:39:08 +00:00
|
|
|
}
|
2024-05-27 15:09:48 +00:00
|
|
|
return nullptr;
|
2012-11-25 20:39:08 +00:00
|
|
|
}
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
static void copy_dive_computer(struct divecomputer &res, const struct divecomputer &a)
|
2013-02-04 05:21:47 +00:00
|
|
|
{
|
2024-05-27 15:09:48 +00:00
|
|
|
res = a;
|
|
|
|
res.samples.clear();
|
|
|
|
res.events.clear();
|
2013-02-04 05:21:47 +00:00
|
|
|
}
|
|
|
|
|
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'.
|
|
|
|
*/
|
2024-06-04 11:22:25 +00:00
|
|
|
static void interleave_dive_computers(struct dive &res,
|
|
|
|
const struct dive &a, const struct dive &b,
|
2024-08-28 04:50:37 +00:00
|
|
|
const int cylinders_map_a[], const int cylinders_map_b[])
|
2012-11-25 20:39:08 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
res.dcs.clear();
|
|
|
|
for (const auto &dc1: a.dcs) {
|
|
|
|
res.dcs.emplace_back();
|
|
|
|
divecomputer &newdc = res.dcs.back();
|
2024-05-27 15:09:48 +00:00
|
|
|
copy_dive_computer(newdc, dc1);
|
|
|
|
const divecomputer *match = find_matching_computer(dc1, b);
|
2012-11-25 20:39:08 +00:00
|
|
|
if (match) {
|
2024-08-28 04:50:37 +00:00
|
|
|
int offset = match->when - dc1.when;
|
2024-06-04 11:22:25 +00:00
|
|
|
merge_events(res, newdc, dc1, *match, cylinders_map_a, cylinders_map_b, offset);
|
|
|
|
merge_samples(newdc, dc1, *match, cylinders_map_a, cylinders_map_b, offset);
|
|
|
|
merge_extra_data(newdc, dc1, *match);
|
2014-05-20 05:01:40 +00:00
|
|
|
/* Use the diveid of the later dive! */
|
|
|
|
if (offset > 0)
|
2024-05-27 15:09:48 +00:00
|
|
|
newdc.diveid = match->diveid;
|
2012-11-25 20:39:08 +00:00
|
|
|
} else {
|
2024-06-04 11:22:25 +00:00
|
|
|
dc_cylinder_renumber(res, res.dcs.back(), cylinders_map_a);
|
2012-11-25 20:39:08 +00:00
|
|
|
}
|
2024-05-27 15:09:48 +00:00
|
|
|
}
|
2012-11-25 20:39:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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
|
|
|
*/
|
2024-06-04 11:22:25 +00:00
|
|
|
static void join_dive_computers(struct dive &d,
|
|
|
|
const struct dive &a, const struct dive &b,
|
2018-08-13 02:47:07 +00:00
|
|
|
const int cylinders_map_a[], const int cylinders_map_b[],
|
2024-05-27 15:09:48 +00:00
|
|
|
bool prefer_downloaded)
|
2012-11-25 20:39:08 +00:00
|
|
|
{
|
2024-06-04 11:22:25 +00:00
|
|
|
d.dcs.clear();
|
|
|
|
if (!a.dcs[0].model.empty() && b.dcs[0].model.empty()) {
|
2024-05-27 15:09:48 +00:00
|
|
|
copy_dc_renumber(d, a, cylinders_map_a);
|
2012-11-25 20:39:08 +00:00
|
|
|
return;
|
|
|
|
}
|
2024-06-04 11:22:25 +00:00
|
|
|
if (!b.dcs[0].model.empty() && a.dcs[0].model.empty()) {
|
2024-05-27 15:09:48 +00:00
|
|
|
copy_dc_renumber(d, b, cylinders_map_b);
|
2012-11-25 20:39:08 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
copy_dc_renumber(d, a, cylinders_map_a);
|
|
|
|
copy_dc_renumber(d, b, cylinders_map_b);
|
2012-11-25 20:39:08 +00:00
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
remove_redundant_dc(d, prefer_downloaded);
|
2012-11-25 20:39:08 +00:00
|
|
|
}
|
|
|
|
|
2024-06-05 15:02:40 +00:00
|
|
|
static bool has_dc_type(const struct dive &dive, bool dc_is_planner)
|
2019-05-30 15:36:44 +00:00
|
|
|
{
|
2024-06-05 15:02:40 +00:00
|
|
|
return std::any_of(dive.dcs.begin(), dive.dcs.end(),
|
2024-05-27 15:09:48 +00:00
|
|
|
[dc_is_planner] (const divecomputer &dc)
|
|
|
|
{ return is_dc_planner(&dc) == dc_is_planner; });
|
2019-01-01 17:49:56 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 00:10:59 +00:00
|
|
|
// Does this dive have a dive computer for which is_dc_planner has value planned
|
2024-06-05 15:02:40 +00:00
|
|
|
bool dive::is_planned() const
|
2024-04-25 00:10:59 +00:00
|
|
|
{
|
2024-06-05 15:02:40 +00:00
|
|
|
return has_dc_type(*this, true);
|
2024-04-25 00:10:59 +00:00
|
|
|
}
|
|
|
|
|
2024-06-05 15:02:40 +00:00
|
|
|
bool dive::is_logged() const
|
2024-04-25 00:10:59 +00:00
|
|
|
{
|
2024-06-05 15:02:40 +00:00
|
|
|
return has_dc_type(*this, false);
|
2024-04-25 00:10:59 +00:00
|
|
|
}
|
|
|
|
|
2024-06-24 18:47:02 +00:00
|
|
|
std::unique_ptr<dive> dive::create_merged_dive(const struct dive &a, const struct dive &b, int offset, bool prefer_downloaded)
|
2012-11-11 06:20:05 +00:00
|
|
|
{
|
2024-06-24 18:47:02 +00:00
|
|
|
auto res = std::make_unique<dive>();
|
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.
|
|
|
|
*/
|
2024-06-24 18:47:02 +00:00
|
|
|
if (a.likely_same(b))
|
2015-09-22 19:32:27 +00:00
|
|
|
offset = 0;
|
2012-11-21 23:34:04 +00:00
|
|
|
}
|
2014-01-11 07:46:05 +00:00
|
|
|
|
2024-06-24 18:47:02 +00:00
|
|
|
res->when = prefer_downloaded ? b.when : a.when;
|
|
|
|
res->selected = a.selected || b.selected;
|
|
|
|
MERGE_TXT(res, a, b, notes, "\n--\n");
|
|
|
|
MERGE_TXT(res, a, b, buddy, ", ");
|
|
|
|
MERGE_TXT(res, a, b, diveguide, ", ");
|
|
|
|
MERGE_MAX(res, a, b, rating);
|
|
|
|
MERGE_TXT(res, a, b, suit, ", ");
|
|
|
|
MERGE_MAX(res, a, b, number);
|
|
|
|
MERGE_NONZERO(res, a, b, visibility);
|
|
|
|
MERGE_NONZERO(res, a, b, wavesize);
|
|
|
|
MERGE_NONZERO(res, a, b, current);
|
|
|
|
MERGE_NONZERO(res, a, b, surge);
|
|
|
|
MERGE_NONZERO(res, a, b, chill);
|
|
|
|
res->pictures = !a.pictures.empty() ? a.pictures : b.pictures;
|
|
|
|
res->tags = taglist_merge(a.tags, b.tags);
|
2022-04-23 22:01:42 +00:00
|
|
|
/* if we get dives without any gas / cylinder information in an import, make sure
|
|
|
|
* that there is at leatst one entry in the cylinder map for that dive */
|
2024-06-24 18:47:02 +00:00
|
|
|
auto cylinders_map_a = std::make_unique<int[]>(std::max(size_t(1), a.cylinders.size()));
|
|
|
|
auto cylinders_map_b = std::make_unique<int[]>(std::max(size_t(1), b.cylinders.size()));
|
|
|
|
merge_cylinders(*res, a, b, cylinders_map_a.get(), cylinders_map_b.get());
|
|
|
|
merge_equipment(*res, a, b);
|
|
|
|
merge_temperatures(*res, a, b);
|
2018-10-03 19:32:28 +00:00
|
|
|
if (prefer_downloaded) {
|
2013-01-23 00:57:07 +00:00
|
|
|
/* If we prefer downloaded, do those first, and get rid of "might be same" computers */
|
2024-06-24 18:47:02 +00:00
|
|
|
join_dive_computers(*res, b, a, cylinders_map_b.get(), cylinders_map_a.get(), true);
|
|
|
|
} else if (offset && might_be_same_device(a.dcs[0], b.dcs[0])) {
|
2024-08-28 04:50:37 +00:00
|
|
|
interleave_dive_computers(*res, a, b, cylinders_map_a.get(), cylinders_map_b.get());
|
2024-05-27 15:09:48 +00:00
|
|
|
} else {
|
2024-06-24 18:47:02 +00:00
|
|
|
join_dive_computers(*res, a, b, cylinders_map_a.get(), cylinders_map_b.get(), false);
|
2024-05-27 15:09:48 +00:00
|
|
|
}
|
2018-08-13 02:47:07 +00:00
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
return res;
|
2011-09-03 20:19:26 +00:00
|
|
|
}
|
2013-01-31 03:09:16 +00:00
|
|
|
|
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.
|
|
|
|
*/
|
2024-05-27 15:09:48 +00:00
|
|
|
static inline int dc_totaltime(const struct divecomputer &dc)
|
2015-09-22 19:32:27 +00:00
|
|
|
{
|
2024-05-27 15:09:48 +00:00
|
|
|
int time = dc.duration.seconds;
|
2015-09-22 19:32:27 +00:00
|
|
|
|
2024-05-27 15:09:48 +00:00
|
|
|
for (auto it = dc.samples.rbegin(); it != dc.samples.rend(); ++it) {
|
2024-05-19 10:38:38 +00:00
|
|
|
time = it->time.seconds;
|
|
|
|
if (it->depth.mm >= SURFACE_THRESHOLD)
|
2015-09-22 19:32:27 +00:00
|
|
|
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).
|
|
|
|
*/
|
2024-06-05 15:02:40 +00:00
|
|
|
duration_t dive::totaltime() const
|
2015-09-22 19:32:27 +00:00
|
|
|
{
|
2024-06-05 15:02:40 +00:00
|
|
|
int time = duration.seconds;
|
2015-09-22 19:32:27 +00:00
|
|
|
|
2024-06-05 15:02:40 +00:00
|
|
|
bool logged = is_logged();
|
|
|
|
for (auto &dc: dcs) {
|
2024-05-27 15:09:48 +00:00
|
|
|
if (logged || !is_dc_planner(&dc)) {
|
|
|
|
int dc_time = dc_totaltime(dc);
|
|
|
|
if (dc_time > time)
|
|
|
|
time = dc_time;
|
|
|
|
}
|
2015-09-22 19:32:27 +00:00
|
|
|
}
|
2024-06-05 15:02:40 +00:00
|
|
|
return { time };
|
2015-09-22 19:32:27 +00:00
|
|
|
}
|
|
|
|
|
2024-06-05 15:02:40 +00:00
|
|
|
timestamp_t dive::endtime() const
|
2015-09-22 19:32:27 +00:00
|
|
|
{
|
2024-06-05 15:02:40 +00:00
|
|
|
return when + totaltime().seconds;
|
2015-09-22 19:32:27 +00:00
|
|
|
}
|
|
|
|
|
2024-06-30 20:02:20 +00:00
|
|
|
bool dive::time_during_dive_with_offset(timestamp_t when, timestamp_t offset) const
|
2015-06-25 05:38:44 +00:00
|
|
|
{
|
2024-06-30 20:02:20 +00:00
|
|
|
timestamp_t start = when;
|
|
|
|
timestamp_t end = endtime();
|
2015-09-22 19:32:27 +00:00
|
|
|
return start - offset <= when && when <= end + offset;
|
2015-06-25 05:38:44 +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 */
|
2024-05-04 16:45:55 +00:00
|
|
|
void set_informational_units(const char *units)
|
2015-06-17 03:28:42 +00:00
|
|
|
{
|
|
|
|
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"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.length = units::METERS;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "FEET"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.length = units::FEET;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "LITER"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.volume = units::LITER;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "CUFT"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.volume = units::CUFT;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "BAR"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.pressure = units::BAR;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "PSI"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.pressure = units::PSI;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "CELSIUS"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.temperature = units::CELSIUS;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "FAHRENHEIT"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.temperature = units::FAHRENHEIT;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "KG"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.weight = units::KG;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "LBS"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.weight = units::LBS;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "SECONDS"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.vertical_speed_time = units::SECONDS;
|
2015-06-17 03:28:42 +00:00
|
|
|
if (strstr(units, "MINUTES"))
|
2024-02-27 11:02:20 +00:00
|
|
|
git_prefs.units.vertical_speed_time = units::MINUTES;
|
2015-06-17 03:28:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-17 20:22:55 +00:00
|
|
|
/* clones a dive and moves given dive computer to front */
|
2024-06-04 05:10:22 +00:00
|
|
|
std::unique_ptr<dive> clone_make_first_dc(const struct dive &d, int dc_number)
|
2014-06-11 20:56:33 +00:00
|
|
|
{
|
2019-05-17 20:22:55 +00:00
|
|
|
/* copy the dive */
|
2024-06-04 05:10:22 +00:00
|
|
|
auto res = std::make_unique<dive>(d);
|
2019-05-17 20:22:55 +00:00
|
|
|
|
|
|
|
/* make a new unique id, since we still can't handle two equal ids */
|
|
|
|
res->id = dive_getUniqID();
|
|
|
|
|
2024-06-04 05:10:22 +00:00
|
|
|
if (dc_number != 0)
|
|
|
|
move_in_range(res->dcs, dc_number, dc_number + 1, 0);
|
2019-05-17 20:22:55 +00:00
|
|
|
|
|
|
|
return res;
|
2014-06-11 20:56:33 +00:00
|
|
|
}
|
|
|
|
|
2016-05-21 09:32:07 +00:00
|
|
|
//Calculate O2 in best mix
|
2024-06-30 19:38:32 +00:00
|
|
|
fraction_t dive::best_o2(depth_t depth, bool in_planner) const
|
2016-05-21 09:32:07 +00:00
|
|
|
{
|
|
|
|
fraction_t fo2;
|
2021-10-16 19:01:40 +00:00
|
|
|
int po2 = in_planner ? prefs.bottompo2 : (int)(prefs.modpO2 * 1000.0);
|
2016-05-21 09:32:07 +00:00
|
|
|
|
2024-06-30 19:38:32 +00:00
|
|
|
fo2.permille = (po2 * 100 / depth_to_mbar(depth.mm)) * 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
|
2024-06-30 19:38:32 +00:00
|
|
|
fraction_t dive::best_he(depth_t depth, bool o2narcotic, fraction_t fo2) const
|
2016-05-21 09:32:07 +00:00
|
|
|
{
|
|
|
|
fraction_t fhe;
|
|
|
|
int pnarcotic, ambient;
|
2024-06-30 19:38:32 +00:00
|
|
|
pnarcotic = depth_to_mbar(prefs.bestmixend.mm);
|
|
|
|
ambient = depth_to_mbar(depth.mm);
|
2019-10-29 16:57:34 +00:00
|
|
|
if (o2narcotic) {
|
|
|
|
fhe.permille = (100 - 100 * pnarcotic / ambient) * 10; //use integer arithmetic to round up to nearest percent
|
|
|
|
} else {
|
|
|
|
fhe.permille = 1000 - fo2.permille - N2_IN_AIR * pnarcotic / ambient;
|
|
|
|
}
|
2016-05-21 09:32:07 +00:00
|
|
|
if (fhe.permille < 0)
|
|
|
|
fhe.permille = 0;
|
|
|
|
return fhe;
|
|
|
|
}
|
2018-07-18 18:47:19 +00:00
|
|
|
|
2024-06-25 12:04:01 +00:00
|
|
|
static constexpr std::array<unsigned char, 20> null_id = {};
|
|
|
|
void dive::invalidate_cache()
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-25 12:04:01 +00:00
|
|
|
git_id = null_id;
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-25 12:04:01 +00:00
|
|
|
bool dive::cache_is_valid() const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-25 12:04:01 +00:00
|
|
|
return git_id != null_id;
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-30 15:26:12 +00:00
|
|
|
pressure_t dive::get_surface_pressure() const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-30 15:26:12 +00:00
|
|
|
return surface_pressure.mbar > 0 ? surface_pressure
|
|
|
|
: pressure_t { SURFACE_PRESSURE };
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2020-09-02 18:14:45 +00:00
|
|
|
/* This returns the conversion factor that you need to multiply
|
|
|
|
* a (relative) depth in mm to obtain a (relative) pressure in mbar.
|
|
|
|
* As everywhere in Subsurface, the expected unit of a salinity is
|
|
|
|
* g/10l such that sea water has a salinity of 10300
|
|
|
|
*/
|
|
|
|
static double salinity_to_specific_weight(int salinity)
|
|
|
|
{
|
|
|
|
return salinity * 0.981 / 100000.0;
|
|
|
|
}
|
|
|
|
|
2018-07-18 18:47:19 +00:00
|
|
|
/* Pa = N/m^2 - so we determine the weight (in N) of the mass of 10m
|
|
|
|
* of water (and use standard salt water at 1.03kg per liter if we don't know salinity)
|
|
|
|
* and add that to the surface pressure (or to 1013 if that's unknown) */
|
2021-12-03 13:59:36 +00:00
|
|
|
static double calculate_depth_to_mbarf(int depth, pressure_t surface_pressure, int salinity)
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
|
|
|
double specific_weight;
|
|
|
|
int mbar = surface_pressure.mbar;
|
|
|
|
|
|
|
|
if (!mbar)
|
|
|
|
mbar = SURFACE_PRESSURE;
|
|
|
|
if (!salinity)
|
|
|
|
salinity = SEAWATER_SALINITY;
|
|
|
|
if (salinity < 500)
|
|
|
|
salinity += FRESHWATER_SALINITY;
|
2020-09-02 18:14:45 +00:00
|
|
|
specific_weight = salinity_to_specific_weight(salinity);
|
2021-12-03 13:59:36 +00:00
|
|
|
return mbar + depth * specific_weight;
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-20 20:09:47 +00:00
|
|
|
int dive::depth_to_mbar(int depth) const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-20 20:09:47 +00:00
|
|
|
return lrint(depth_to_mbarf(depth));
|
2021-12-03 13:59:36 +00:00
|
|
|
}
|
|
|
|
|
2024-06-20 20:09:47 +00:00
|
|
|
double dive::depth_to_mbarf(int depth) const
|
2021-12-03 13:59:36 +00:00
|
|
|
{
|
2024-05-23 04:06:23 +00:00
|
|
|
// For downloaded and planned dives, use DC's values
|
2024-06-20 20:09:47 +00:00
|
|
|
int salinity = dcs[0].salinity;
|
|
|
|
pressure_t surface_pressure = dcs[0].surface_pressure;
|
2023-06-02 05:16:14 +00:00
|
|
|
|
2024-06-20 20:09:47 +00:00
|
|
|
if (is_dc_manually_added_dive(&dcs[0])) { // For manual dives, salinity and pressure in another place...
|
|
|
|
surface_pressure = this->surface_pressure;
|
|
|
|
salinity = user_salinity;
|
2023-06-02 05:16:14 +00:00
|
|
|
}
|
|
|
|
return calculate_depth_to_mbarf(depth, surface_pressure, salinity);
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-20 20:09:47 +00:00
|
|
|
double dive::depth_to_bar(int depth) const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-20 20:09:47 +00:00
|
|
|
return depth_to_mbar(depth) / 1000.0;
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-20 20:09:47 +00:00
|
|
|
double dive::depth_to_atm(int depth) const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-20 20:09:47 +00:00
|
|
|
return mbar_to_atm(depth_to_mbar(depth));
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* for the inverse calculation we use just the relative pressure
|
|
|
|
* (that's the one that some dive computers like the Uemis Zurich
|
|
|
|
* provide - for the other models that do this libdivecomputer has to
|
|
|
|
* take care of this, but the Uemis we support natively */
|
2024-06-20 20:47:16 +00:00
|
|
|
int dive::rel_mbar_to_depth(int mbar) const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-05-23 04:06:23 +00:00
|
|
|
// For downloaded and planned dives, use DC's salinity. Manual dives, use user's salinity
|
2024-06-20 20:47:16 +00:00
|
|
|
int salinity = is_dc_manually_added_dive(&dcs[0]) ? user_salinity : dcs[0].salinity;
|
2023-06-02 05:16:14 +00:00
|
|
|
if (!salinity)
|
|
|
|
salinity = SEAWATER_SALINITY;
|
|
|
|
|
2018-07-18 18:47:19 +00:00
|
|
|
/* whole mbar gives us cm precision */
|
2023-06-02 05:16:14 +00:00
|
|
|
double specific_weight = salinity_to_specific_weight(salinity);
|
2020-09-02 18:14:45 +00:00
|
|
|
return (int)lrint(mbar / specific_weight);
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-20 20:47:16 +00:00
|
|
|
int dive::mbar_to_depth(int mbar) const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-05-23 04:06:23 +00:00
|
|
|
// For downloaded and planned dives, use DC's pressure. Manual dives, use user's pressure
|
2024-06-20 20:47:16 +00:00
|
|
|
pressure_t surface_pressure = is_dc_manually_added_dive(&dcs[0])
|
|
|
|
? this->surface_pressure
|
|
|
|
: dcs[0].surface_pressure;
|
2023-06-02 05:16:14 +00:00
|
|
|
|
|
|
|
if (!surface_pressure.mbar)
|
2018-07-18 18:47:19 +00:00
|
|
|
surface_pressure.mbar = SURFACE_PRESSURE;
|
2024-05-15 05:23:39 +00:00
|
|
|
|
2024-06-20 20:47:16 +00:00
|
|
|
return rel_mbar_to_depth(mbar - surface_pressure.mbar);
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* MOD rounded to multiples of roundto mm */
|
2024-06-30 14:17:20 +00:00
|
|
|
depth_t dive::gas_mod(struct gasmix mix, pressure_t po2_limit, int roundto) const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-30 14:17:20 +00:00
|
|
|
double depth = (double) mbar_to_depth(po2_limit.mbar * 1000 / get_o2(mix));
|
|
|
|
return depth_t { (int)lrint(depth / roundto) * roundto };
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Maximum narcotic depth rounded to multiples of roundto mm */
|
2024-06-30 14:17:20 +00:00
|
|
|
depth_t dive::gas_mnd(struct gasmix mix, depth_t end, int roundto) const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-30 14:17:20 +00:00
|
|
|
pressure_t ppo2n2 { depth_to_mbar(end.mm) };
|
2018-07-18 18:47:19 +00:00
|
|
|
|
2020-07-11 11:15:21 +00:00
|
|
|
int maxambient = prefs.o2narcotic ?
|
|
|
|
(int)lrint(ppo2n2.mbar / (1 - get_he(mix) / 1000.0))
|
|
|
|
:
|
2020-11-13 08:38:08 +00:00
|
|
|
get_n2(mix) > 0 ?
|
|
|
|
(int)lrint(ppo2n2.mbar * N2_IN_AIR / get_n2(mix))
|
|
|
|
:
|
|
|
|
// Actually: Infinity
|
|
|
|
1000000;
|
2024-06-30 14:17:20 +00:00
|
|
|
return depth_t { (int)lrint(((double)mbar_to_depth(maxambient)) / roundto) * roundto };
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-30 18:55:34 +00:00
|
|
|
std::string dive::get_country() const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-30 18:55:34 +00:00
|
|
|
return dive_site ? taxonomy_get_country(dive_site->taxonomy) : std::string();
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-30 18:55:34 +00:00
|
|
|
std::string dive::get_location() const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-30 18:55:34 +00:00
|
|
|
return dive_site ? dive_site->name : std::string();
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-30 15:16:14 +00:00
|
|
|
int dive::number_of_computers() const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-30 15:16:14 +00:00
|
|
|
return static_cast<int>(dcs.size());
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-30 18:38:12 +00:00
|
|
|
struct divecomputer *dive::get_dc(int nr)
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-30 18:38:12 +00:00
|
|
|
if (dcs.empty()) // Can't happen!
|
2018-07-18 18:47:19 +00:00
|
|
|
return NULL;
|
2024-05-27 15:09:48 +00:00
|
|
|
nr = std::max(0, nr);
|
2024-06-30 18:38:12 +00:00
|
|
|
return &dcs[static_cast<size_t>(nr) % dcs.size()];
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2024-06-30 18:38:12 +00:00
|
|
|
const struct divecomputer *dive::get_dc(int nr) const
|
2021-01-09 17:58:33 +00:00
|
|
|
{
|
2024-06-30 18:38:12 +00:00
|
|
|
return const_cast<dive &>(*this).get_dc(nr);
|
2021-01-09 17:58:33 +00:00
|
|
|
}
|
|
|
|
|
2024-06-30 14:33:52 +00:00
|
|
|
bool dive::dive_has_gps_location() const
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2024-06-30 15:38:36 +00:00
|
|
|
return dive_site && dive_site->has_gps_location();
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
|
|
|
|
2019-04-24 21:59:59 +00:00
|
|
|
/* Extract GPS location of a dive computer stored in the GPS1
|
|
|
|
* or GPS2 extra data fields */
|
2024-06-30 14:33:52 +00:00
|
|
|
static location_t dc_get_gps_location(const struct divecomputer &dc)
|
2019-04-24 21:59:59 +00:00
|
|
|
{
|
2024-05-18 19:04:58 +00:00
|
|
|
location_t res;
|
2019-04-24 21:59:59 +00:00
|
|
|
|
2024-06-30 14:33:52 +00:00
|
|
|
for (const auto &data: dc.extra_data) {
|
2024-05-18 19:04:58 +00:00
|
|
|
if (data.key == "GPS1") {
|
|
|
|
parse_location(data.value.c_str(), &res);
|
2019-04-24 21:59:59 +00:00
|
|
|
/* If we found a valid GPS1 field exit early since
|
|
|
|
* it has priority over GPS2 */
|
|
|
|
if (has_location(&res))
|
|
|
|
break;
|
2024-05-18 19:04:58 +00:00
|
|
|
} else if (data.key == "GPS2") {
|
2019-04-24 21:59:59 +00:00
|
|
|
/* For GPS2 fields continue searching, as we might
|
|
|
|
* still find a GPS1 field */
|
2024-05-18 19:04:58 +00:00
|
|
|
parse_location(data.value.c_str(), &res);
|
2019-04-24 21:59:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get GPS location for a dive. Highest priority is given to the GPS1
|
|
|
|
* extra data written by libdivecomputer, as this comes from a real GPS
|
|
|
|
* device. If that doesn't exits, use the currently set dive site.
|
|
|
|
* This function is potentially slow, therefore only call sparingly
|
|
|
|
* and remember the result.
|
|
|
|
*/
|
2024-06-30 14:33:52 +00:00
|
|
|
location_t dive::get_gps_location() const
|
2019-04-24 21:59:59 +00:00
|
|
|
{
|
2024-06-30 14:33:52 +00:00
|
|
|
for (const struct divecomputer &dc: dcs) {
|
|
|
|
location_t res = dc_get_gps_location(dc);
|
2019-04-24 21:59:59 +00:00
|
|
|
if (has_location(&res))
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No libdivecomputer generated GPS data found.
|
|
|
|
* Let's use the location of the current dive site.
|
|
|
|
*/
|
2024-06-30 14:33:52 +00:00
|
|
|
return dive_site ? dive_site->location : location_t();
|
2019-04-24 21:59:59 +00:00
|
|
|
}
|
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
gasmix_loop::gasmix_loop(const struct dive &d, const struct divecomputer &dc) :
|
|
|
|
dive(d), dc(dc), last(gasmix_air), loop("gaschange")
|
2018-07-18 18:47:19 +00:00
|
|
|
{
|
2019-08-04 16:44:57 +00:00
|
|
|
/* if there is no cylinder, return air */
|
2024-05-28 19:31:11 +00:00
|
|
|
if (dive.cylinders.empty())
|
2024-05-25 06:16:57 +00:00
|
|
|
return;
|
2019-08-04 16:44:57 +00:00
|
|
|
|
2024-05-25 06:16:57 +00:00
|
|
|
/* on first invocation, get initial gas mix and first event (if any) */
|
2024-06-25 05:43:32 +00:00
|
|
|
int cyl = dive.explicit_first_cylinder(&dc);
|
|
|
|
last = dive.get_cylinder(cyl)->gasmix;
|
2024-05-25 06:16:57 +00:00
|
|
|
ev = loop.next(dc);
|
|
|
|
}
|
|
|
|
|
|
|
|
gasmix gasmix_loop::next(int time)
|
|
|
|
{
|
|
|
|
/* if there is no cylinder, return air */
|
2024-05-28 19:31:11 +00:00
|
|
|
if (dive.cylinders.empty())
|
2024-05-25 06:16:57 +00:00
|
|
|
return last;
|
2018-08-16 11:35:14 +00:00
|
|
|
|
2018-10-29 22:55:38 +00:00
|
|
|
while (ev && ev->time.seconds <= time) {
|
2024-06-25 05:43:32 +00:00
|
|
|
last = dive.get_gasmix_from_event(*ev);
|
2024-05-25 06:16:57 +00:00
|
|
|
ev = loop.next(dc);
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
2024-05-25 06:16:57 +00:00
|
|
|
return last;
|
2018-07-18 18:47:19 +00:00
|
|
|
}
|
2018-08-16 15:11:51 +00:00
|
|
|
|
|
|
|
/* get the gas at a certain time during the dive */
|
2018-10-30 11:19:00 +00:00
|
|
|
/* If there is a gasswitch at that time, it returns the new gasmix */
|
2024-06-25 05:43:32 +00:00
|
|
|
struct gasmix dive::get_gasmix_at_time(const struct divecomputer &dc, duration_t time) const
|
2018-08-16 15:11:51 +00:00
|
|
|
{
|
2024-06-25 05:43:32 +00:00
|
|
|
return gasmix_loop(*this, dc).next(time.seconds);
|
2018-08-16 15:11:51 +00:00
|
|
|
}
|
2021-09-02 16:39:17 +00:00
|
|
|
|
|
|
|
/* Does that cylinder have any pressure readings? */
|
2024-05-04 16:45:55 +00:00
|
|
|
bool cylinder_with_sensor_sample(const struct dive *dive, int cylinder_id)
|
2021-09-02 16:39:17 +00:00
|
|
|
{
|
2024-05-27 15:09:48 +00:00
|
|
|
for (const auto &dc: dive->dcs) {
|
|
|
|
for (const auto &sample: dc.samples) {
|
2021-09-02 16:39:17 +00:00
|
|
|
for (int j = 0; j < MAX_SENSORS; ++j) {
|
2024-05-19 10:38:38 +00:00
|
|
|
if (!sample.pressure[j].mbar)
|
2022-04-28 19:30:10 +00:00
|
|
|
continue;
|
2024-05-19 10:38:38 +00:00
|
|
|
if (sample.sensor[j] == cylinder_id)
|
2021-09-02 16:39:17 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2024-05-27 15:09:48 +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)
|
|
|
|
*/
|
2024-06-05 15:02:40 +00:00
|
|
|
temperature_t dive::dc_watertemp() const
|
2024-05-27 15:09:48 +00:00
|
|
|
{
|
|
|
|
int sum = 0, nr = 0;
|
|
|
|
|
2024-06-05 15:02:40 +00:00
|
|
|
for (auto &dc: dcs) {
|
2024-05-27 15:09:48 +00:00
|
|
|
if (dc.watertemp.mkelvin) {
|
|
|
|
sum += dc.watertemp.mkelvin;
|
|
|
|
nr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!nr)
|
|
|
|
return temperature_t();
|
|
|
|
return temperature_t{ static_cast<uint32_t>((sum + nr / 2) / nr) };
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* What do the dive computers say the air temperature is?
|
|
|
|
*/
|
2024-06-05 15:02:40 +00:00
|
|
|
temperature_t dive::dc_airtemp() const
|
2024-05-27 15:09:48 +00:00
|
|
|
{
|
|
|
|
int sum = 0, nr = 0;
|
|
|
|
|
2024-06-05 15:02:40 +00:00
|
|
|
for (auto &dc: dcs) {
|
2024-05-27 15:09:48 +00:00
|
|
|
if (dc.airtemp.mkelvin) {
|
|
|
|
sum += dc.airtemp.mkelvin;
|
|
|
|
nr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!nr)
|
|
|
|
return temperature_t();
|
|
|
|
return temperature_t{ static_cast<uint32_t>((sum + nr / 2) / nr) };
|
|
|
|
}
|
2024-06-24 19:04:31 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Get "maximal" dive gas for a dive.
|
|
|
|
* Rules:
|
|
|
|
* - Trimix trumps nitrox (highest He wins, O2 breaks ties)
|
|
|
|
* - Nitrox trumps air (even if hypoxic)
|
|
|
|
* These are the same rules as the inter-dive sorting rules.
|
|
|
|
*/
|
|
|
|
dive::get_maximal_gas_result dive::get_maximal_gas() const
|
|
|
|
{
|
|
|
|
int maxo2 = -1, maxhe = -1, mino2 = 1000;
|
|
|
|
|
|
|
|
for (auto [i, cyl]: enumerated_range(cylinders)) {
|
|
|
|
int o2 = get_o2(cyl.gasmix);
|
|
|
|
int he = get_he(cyl.gasmix);
|
|
|
|
|
2024-06-25 05:43:32 +00:00
|
|
|
if (!is_cylinder_used(i))
|
2024-06-24 19:04:31 +00:00
|
|
|
continue;
|
|
|
|
if (cyl.cylinder_use == OXYGEN)
|
|
|
|
continue;
|
|
|
|
if (cyl.cylinder_use == NOT_USED)
|
|
|
|
continue;
|
|
|
|
if (o2 > maxo2)
|
|
|
|
maxo2 = o2;
|
|
|
|
if (o2 < mino2 && maxhe <= 0)
|
|
|
|
mino2 = o2;
|
|
|
|
if (he > maxhe) {
|
|
|
|
maxhe = he;
|
|
|
|
mino2 = o2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* All air? Show/sort as "air"/zero */
|
|
|
|
if ((!maxhe && maxo2 == O2_IN_AIR && mino2 == maxo2) ||
|
|
|
|
(maxo2 == -1 && maxhe == -1 && mino2 == 1000))
|
|
|
|
maxo2 = mino2 = 0;
|
|
|
|
return { mino2, maxhe, maxo2 };
|
|
|
|
}
|
2024-06-25 05:43:32 +00:00
|
|
|
|
|
|
|
bool dive::has_gaschange_event(const struct divecomputer *dc, int idx) const
|
|
|
|
{
|
|
|
|
bool first_gas_explicit = false;
|
|
|
|
event_loop loop("gaschange");
|
|
|
|
while (auto event = loop.next(*dc)) {
|
|
|
|
if (!dc->samples.empty() && (event->time.seconds == 0 ||
|
|
|
|
(dc->samples[0].time.seconds == event->time.seconds)))
|
|
|
|
first_gas_explicit = true;
|
|
|
|
if (get_cylinder_index(*event) == idx)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return !first_gas_explicit && idx == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool dive::is_cylinder_used(int idx) const
|
|
|
|
{
|
|
|
|
if (idx < 0 || static_cast<size_t>(idx) >= cylinders.size())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const cylinder_t &cyl = cylinders[idx];
|
|
|
|
if ((cyl.start.mbar - cyl.end.mbar) > SOME_GAS)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if ((cyl.sample_start.mbar - cyl.sample_end.mbar) > SOME_GAS)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
for (auto &dc: dcs) {
|
|
|
|
if (has_gaschange_event(&dc, idx))
|
|
|
|
return true;
|
|
|
|
else if (dc.divemode == CCR && idx == get_cylinder_idx_by_use(*this, OXYGEN))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool dive::is_cylinder_prot(int idx) const
|
|
|
|
{
|
|
|
|
if (idx < 0 || static_cast<size_t>(idx) >= cylinders.size())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return std::any_of(dcs.begin(), dcs.end(),
|
|
|
|
[this, idx](auto &dc)
|
|
|
|
{ return has_gaschange_event(&dc, idx); });
|
|
|
|
}
|
2024-06-30 09:13:39 +00:00
|
|
|
|
|
|
|
weight_t dive::total_weight() const
|
|
|
|
{
|
|
|
|
// TODO: implement addition for units.h types
|
|
|
|
return std::accumulate(weightsystems.begin(), weightsystems.end(), weight_t(),
|
|
|
|
[] (weight_t w, const weightsystem_t &ws)
|
|
|
|
{ return weight_t{ w.grams + ws.weight.grams }; });
|
|
|
|
}
|