2011-09-20 19:40:34 +00:00
|
|
|
/* dive.c */
|
|
|
|
/* maintains the internal dive list structure */
|
2011-09-03 20:19:26 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
2012-10-11 00:42:59 +00:00
|
|
|
#include <glib/gi18n.h>
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
#include "dive.h"
|
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
void add_event(struct divecomputer *dc, int time, int type, int flags, int value, const char *name)
|
2011-09-23 01:02:54 +00:00
|
|
|
{
|
|
|
|
struct event *ev, **p;
|
|
|
|
unsigned int size, len = strlen(name);
|
|
|
|
|
|
|
|
size = sizeof(*ev) + len + 1;
|
|
|
|
ev = malloc(size);
|
|
|
|
if (!ev)
|
|
|
|
return;
|
|
|
|
memset(ev, 0, size);
|
|
|
|
memcpy(ev->name, name, len);
|
|
|
|
ev->time.seconds = time;
|
|
|
|
ev->type = type;
|
|
|
|
ev->flags = flags;
|
|
|
|
ev->value = value;
|
|
|
|
ev->next = NULL;
|
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
p = &dc->events;
|
2011-09-23 01:02:54 +00:00
|
|
|
while (*p)
|
|
|
|
p = &(*p)->next;
|
|
|
|
*p = ev;
|
2011-10-25 07:29:19 +00:00
|
|
|
remember_event(name);
|
2011-09-23 01:02:54 +00:00
|
|
|
}
|
|
|
|
|
2011-11-02 03:13:14 +00:00
|
|
|
int get_pressure_units(unsigned int mb, const char **units)
|
|
|
|
{
|
|
|
|
int pressure;
|
|
|
|
const char* unit;
|
2013-01-11 01:26:10 +00:00
|
|
|
struct units *units_p = get_units();
|
2011-11-02 03:13:14 +00:00
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
switch (units_p->pressure) {
|
2011-11-02 03:13:14 +00:00
|
|
|
case PASCAL:
|
|
|
|
pressure = mb * 100;
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("pascal");
|
2011-11-02 03:13:14 +00:00
|
|
|
break;
|
|
|
|
case BAR:
|
|
|
|
pressure = (mb + 500) / 1000;
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("bar");
|
2011-11-02 03:13:14 +00:00
|
|
|
break;
|
|
|
|
case PSI:
|
|
|
|
pressure = mbar_to_PSI(mb);
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("psi");
|
2011-11-02 03:13:14 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (units)
|
|
|
|
*units = unit;
|
|
|
|
return pressure;
|
|
|
|
}
|
|
|
|
|
2011-11-01 18:39:52 +00:00
|
|
|
double get_temp_units(unsigned int mk, const char **units)
|
|
|
|
{
|
|
|
|
double deg;
|
|
|
|
const char *unit;
|
2013-01-11 01:26:10 +00:00
|
|
|
struct units *units_p = get_units();
|
2011-11-01 18:39:52 +00:00
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
if (units_p->temperature == FAHRENHEIT) {
|
2011-11-01 18:39:52 +00:00
|
|
|
deg = mkelvin_to_F(mk);
|
|
|
|
unit = UTF8_DEGREE "F";
|
|
|
|
} else {
|
|
|
|
deg = mkelvin_to_C(mk);
|
|
|
|
unit = UTF8_DEGREE "C";
|
|
|
|
}
|
|
|
|
if (units)
|
|
|
|
*units = unit;
|
|
|
|
return deg;
|
|
|
|
}
|
|
|
|
|
2011-11-02 02:56:14 +00:00
|
|
|
double get_volume_units(unsigned int ml, int *frac, const char **units)
|
|
|
|
{
|
|
|
|
int decimals;
|
|
|
|
double vol;
|
|
|
|
const char *unit;
|
2013-01-11 01:26:10 +00:00
|
|
|
struct units *units_p = get_units();
|
2011-11-02 02:56:14 +00:00
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
switch (units_p->volume) {
|
2011-11-02 02:56:14 +00:00
|
|
|
case LITER:
|
|
|
|
vol = ml / 1000.0;
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("l");
|
2011-11-02 02:56:14 +00:00
|
|
|
decimals = 1;
|
|
|
|
break;
|
|
|
|
case CUFT:
|
|
|
|
vol = ml_to_cuft(ml);
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("cuft");
|
2011-11-02 02:56:14 +00:00
|
|
|
decimals = 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (frac)
|
|
|
|
*frac = decimals;
|
|
|
|
if (units)
|
|
|
|
*units = unit;
|
|
|
|
return vol;
|
|
|
|
}
|
|
|
|
|
2011-09-21 19:12:54 +00:00
|
|
|
double get_depth_units(unsigned int mm, int *frac, const char **units)
|
|
|
|
{
|
|
|
|
int decimals;
|
|
|
|
double d;
|
|
|
|
const char *unit;
|
2013-01-11 01:26:10 +00:00
|
|
|
struct units *units_p = get_units();
|
2011-09-21 19:12:54 +00:00
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
switch (units_p->length) {
|
2011-09-21 19:12:54 +00:00
|
|
|
case METERS:
|
|
|
|
d = mm / 1000.0;
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("m");
|
2011-09-21 19:12:54 +00:00
|
|
|
decimals = d < 20;
|
|
|
|
break;
|
|
|
|
case FEET:
|
|
|
|
d = mm_to_feet(mm);
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("ft");
|
2011-09-21 19:12:54 +00:00
|
|
|
decimals = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (frac)
|
|
|
|
*frac = decimals;
|
|
|
|
if (units)
|
|
|
|
*units = unit;
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
2012-08-07 18:24:40 +00:00
|
|
|
double get_weight_units(unsigned int grams, int *frac, const char **units)
|
|
|
|
{
|
|
|
|
int decimals;
|
|
|
|
double value;
|
|
|
|
const char* unit;
|
2013-01-11 01:26:10 +00:00
|
|
|
struct units *units_p = get_units();
|
2012-08-07 18:24:40 +00:00
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
if (units_p->weight == LBS) {
|
2012-08-07 18:24:40 +00:00
|
|
|
value = grams_to_lbs(grams);
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("lbs");
|
2012-08-07 18:24:40 +00:00
|
|
|
decimals = 0;
|
|
|
|
} else {
|
|
|
|
value = grams / 1000.0;
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("kg");
|
2012-08-07 18:24:40 +00:00
|
|
|
decimals = 1;
|
|
|
|
}
|
|
|
|
if (frac)
|
|
|
|
*frac = decimals;
|
|
|
|
if (units)
|
|
|
|
*units = unit;
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2011-09-12 19:56:34 +00:00
|
|
|
struct dive *alloc_dive(void)
|
|
|
|
{
|
|
|
|
struct dive *dive;
|
|
|
|
|
2012-11-24 02:05:38 +00:00
|
|
|
dive = malloc(sizeof(*dive));
|
2011-09-12 19:56:34 +00:00
|
|
|
if (!dive)
|
|
|
|
exit(1);
|
2012-11-24 02:05:38 +00:00
|
|
|
memset(dive, 0, sizeof(*dive));
|
2011-09-12 19:56:34 +00:00
|
|
|
return dive;
|
|
|
|
}
|
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
struct sample *prepare_sample(struct divecomputer *dc)
|
2011-09-12 19:56:34 +00:00
|
|
|
{
|
2012-11-24 02:51:27 +00:00
|
|
|
if (dc) {
|
|
|
|
int nr = dc->samples;
|
|
|
|
int alloc_samples = dc->alloc_samples;
|
2011-09-12 19:56:34 +00:00
|
|
|
struct sample *sample;
|
|
|
|
if (nr >= alloc_samples) {
|
2012-11-24 02:05:38 +00:00
|
|
|
struct sample *newsamples;
|
2011-09-12 19:56:34 +00:00
|
|
|
|
|
|
|
alloc_samples = (alloc_samples * 3)/2 + 10;
|
2012-11-24 02:51:27 +00:00
|
|
|
newsamples = realloc(dc->sample, alloc_samples * sizeof(struct sample));
|
2012-11-24 02:05:38 +00:00
|
|
|
if (!newsamples)
|
2011-09-12 19:56:34 +00:00
|
|
|
return NULL;
|
2012-11-24 02:51:27 +00:00
|
|
|
dc->alloc_samples = alloc_samples;
|
|
|
|
dc->sample = newsamples;
|
2011-09-12 19:56:34 +00:00
|
|
|
}
|
2012-11-24 02:51:27 +00:00
|
|
|
sample = dc->sample + nr;
|
2011-09-12 19:56:34 +00:00
|
|
|
memset(sample, 0, sizeof(*sample));
|
|
|
|
return sample;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
void finish_sample(struct divecomputer *dc)
|
2011-09-12 19:56:34 +00:00
|
|
|
{
|
2012-11-24 02:51:27 +00:00
|
|
|
dc->samples++;
|
2011-09-12 19:56:34 +00:00
|
|
|
}
|
|
|
|
|
2011-09-03 20:36:25 +00:00
|
|
|
/*
|
|
|
|
* So when we re-calculate maxdepth and meandepth, we will
|
|
|
|
* not override the old numbers if they are close to the
|
|
|
|
* new ones.
|
|
|
|
*
|
|
|
|
* Why? Because a dive computer may well actually track the
|
|
|
|
* max depth and mean depth at finer granularity than the
|
|
|
|
* samples it stores. So it's possible that the max and mean
|
|
|
|
* have been reported more correctly originally.
|
|
|
|
*
|
2011-09-04 01:48:39 +00:00
|
|
|
* Only if the values calculated from the samples are clearly
|
2011-09-03 20:36:25 +00:00
|
|
|
* different do we override the normal depth values.
|
|
|
|
*
|
|
|
|
* This considers 1m to be "clearly different". That's
|
|
|
|
* a totally random number.
|
|
|
|
*/
|
|
|
|
static void update_depth(depth_t *depth, int new)
|
|
|
|
{
|
2011-09-04 20:06:47 +00:00
|
|
|
if (new) {
|
|
|
|
int old = depth->mm;
|
2011-09-03 20:36:25 +00:00
|
|
|
|
2011-09-04 20:06:47 +00:00
|
|
|
if (abs(old - new) > 1000)
|
|
|
|
depth->mm = new;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_duration(duration_t *duration, int new)
|
|
|
|
{
|
|
|
|
if (new)
|
|
|
|
duration->seconds = new;
|
|
|
|
}
|
|
|
|
|
2013-02-08 06:48:07 +00:00
|
|
|
int get_duration_in_sec(struct dive *dive)
|
|
|
|
{
|
|
|
|
int duration;
|
|
|
|
struct divecomputer *dc = &dive->dc;
|
|
|
|
do {
|
|
|
|
duration = dc->duration.seconds;
|
|
|
|
dc = dc->next;
|
|
|
|
} while (dc);
|
|
|
|
return duration;
|
|
|
|
}
|
|
|
|
|
2011-09-04 20:06:47 +00:00
|
|
|
static void update_temperature(temperature_t *temperature, int new)
|
|
|
|
{
|
|
|
|
if (new) {
|
|
|
|
int old = temperature->mkelvin;
|
|
|
|
|
|
|
|
if (abs(old - new) > 1000)
|
|
|
|
temperature->mkelvin = new;
|
|
|
|
}
|
2011-09-03 20:36:25 +00:00
|
|
|
}
|
|
|
|
|
2011-11-09 15:37:25 +00:00
|
|
|
static void fixup_pressure(struct dive *dive, struct sample *sample)
|
2011-09-05 16:12:54 +00:00
|
|
|
{
|
|
|
|
unsigned int pressure, index;
|
|
|
|
cylinder_t *cyl;
|
|
|
|
|
|
|
|
pressure = sample->cylinderpressure.mbar;
|
|
|
|
if (!pressure)
|
2011-11-09 15:37:25 +00:00
|
|
|
return;
|
First step in cleaning up cylinder pressure sensor logic
This clarifies/changes the meaning of our "cylinderindex" entry in our
samples. It has been rather confused, because different dive computers
have done things differently, and the naming really hasn't helped.
There are two totally different - and independent - cylinder "indexes":
- the pressure sensor index, which indicates which cylinder the sensor
data is from.
- the "active cylinder" index, which indicates which cylinder we actually
breathe from.
These two values really are totally independent, and have nothing
what-so-ever to do with each other. The sensor index may well be fixed:
many dive computers only support a single pressure sensor (whether
wireless or wired), and the sensor index is thus always zero.
Other dive computers may support multiple pressure sensors, and the gas
switch event may - or may not - indicate that the sensor changed too. A
dive computer might give the sensor data for *all* cylinders it can read,
regardless of which one is the one we're actively breathing. In fact, some
dive computers might give sensor data for not just *your* cylinder, but
your buddies.
This patch renames "cylinderindex" in the samples as "sensor", making it
quite clear that it's about which sensor index the pressure data in the
sample is about.
The way we figure out which is the currently active gas is with an
explicit has change event. If a computer (like the Uemis Zurich) joins the
two concepts together, then a sensor change should also create a gas
switch event. This patch also changes the Uemis importer to do that.
Finally, it should be noted that the plot info works totally separately
from the sample data, and is about what we actually *display*, not about
the sample pressures etc. In the plot info, the "cylinderindex" does in
fact mean the currently active cylinder, and while it is initially set to
match the sensor information from the samples, we then walk the gas change
events and fix it up - and if the active cylinder differs from the sensor
cylinder, we clear the sensor data.
[Dirk Hohndel: this conflicted with some of my recent changes - I think
I merged things correctly...]
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
|
|
|
index = sample->sensor;
|
|
|
|
|
|
|
|
/* FIXME! sensor -> cylinder mapping? */
|
2011-09-05 16:12:54 +00:00
|
|
|
if (index >= MAX_CYLINDERS)
|
2011-11-09 15:37:25 +00:00
|
|
|
return;
|
2011-09-05 16:12:54 +00:00
|
|
|
cyl = dive->cylinder + index;
|
2011-11-09 15:37:25 +00:00
|
|
|
if (!cyl->sample_start.mbar)
|
|
|
|
cyl->sample_start.mbar = pressure;
|
|
|
|
cyl->sample_end.mbar = pressure;
|
2011-09-05 16:12:54 +00:00
|
|
|
}
|
|
|
|
|
2013-01-24 18:58:59 +00:00
|
|
|
static void update_min_max_temperatures(struct dive *dive, struct sample *sample)
|
|
|
|
{
|
|
|
|
if (sample->temperature.mkelvin) {
|
|
|
|
if (!dive->maxtemp.mkelvin || sample->temperature.mkelvin > dive->maxtemp.mkelvin)
|
|
|
|
dive->maxtemp = sample->temperature;
|
|
|
|
if (!dive->mintemp.mkelvin || sample->temperature.mkelvin < dive->mintemp.mkelvin)
|
|
|
|
dive->mintemp = sample->temperature;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-12-30 21:09:17 +00:00
|
|
|
static void sanitize_gasmix(struct gasmix *mix)
|
|
|
|
{
|
|
|
|
unsigned int o2, he;
|
|
|
|
|
|
|
|
o2 = mix->o2.permille;
|
|
|
|
he = mix->he.permille;
|
|
|
|
|
|
|
|
/* Regular air: leave empty */
|
|
|
|
if (!he) {
|
|
|
|
if (!o2)
|
|
|
|
return;
|
2013-01-14 22:53:38 +00:00
|
|
|
/* 20.8% to 21% O2 is just air */
|
|
|
|
if (o2 >= (O2_IN_AIR - 1) && o2 <= (O2_IN_AIR + 1)) {
|
2011-12-30 21:09:17 +00:00
|
|
|
mix->o2.permille = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sane mix? */
|
|
|
|
if (o2 <= 1000 && he <= 1000 && o2+he <= 1000)
|
|
|
|
return;
|
2012-09-18 23:51:48 +00:00
|
|
|
fprintf(stderr, "Odd gasmix: %u O2 %u He\n", o2, he);
|
2011-12-30 21:09:17 +00:00
|
|
|
memset(mix, 0, sizeof(*mix));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if the size/workingpressure looks like some standard cylinder
|
|
|
|
* size, eg "AL80".
|
|
|
|
*/
|
|
|
|
static void match_standard_cylinder(cylinder_type_t *type)
|
|
|
|
{
|
|
|
|
double cuft;
|
|
|
|
int psi, len;
|
|
|
|
const char *fmt;
|
|
|
|
char buffer[20], *p;
|
|
|
|
|
|
|
|
/* Do we already have a cylinder description? */
|
|
|
|
if (type->description)
|
|
|
|
return;
|
|
|
|
|
|
|
|
cuft = ml_to_cuft(type->size.mliter);
|
|
|
|
cuft *= to_ATM(type->workingpressure);
|
|
|
|
psi = to_PSI(type->workingpressure);
|
|
|
|
|
|
|
|
switch (psi) {
|
|
|
|
case 2300 ... 2500: /* 2400 psi: LP tank */
|
|
|
|
fmt = "LP%d";
|
|
|
|
break;
|
|
|
|
case 2600 ... 2700: /* 2640 psi: LP+10% */
|
|
|
|
fmt = "LP%d";
|
|
|
|
break;
|
|
|
|
case 2900 ... 3100: /* 3000 psi: ALx tank */
|
|
|
|
fmt = "AL%d";
|
|
|
|
break;
|
|
|
|
case 3400 ... 3500: /* 3442 psi: HP tank */
|
|
|
|
fmt = "HP%d";
|
|
|
|
break;
|
|
|
|
case 3700 ... 3850: /* HP+10% */
|
|
|
|
fmt = "HP%d+";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
len = snprintf(buffer, sizeof(buffer), fmt, (int) (cuft+0.5));
|
|
|
|
p = malloc(len+1);
|
|
|
|
if (!p)
|
|
|
|
return;
|
|
|
|
memcpy(p, buffer, len+1);
|
|
|
|
type->description = p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* There are two ways to give cylinder size information:
|
|
|
|
* - total amount of gas in cuft (depends on working pressure and physical size)
|
|
|
|
* - physical size
|
|
|
|
*
|
|
|
|
* where "physical size" is the one that actually matters and is sane.
|
|
|
|
*
|
|
|
|
* We internally use physical size only. But we save the workingpressure
|
|
|
|
* so that we can do the conversion if required.
|
|
|
|
*/
|
|
|
|
static void sanitize_cylinder_type(cylinder_type_t *type)
|
|
|
|
{
|
|
|
|
double volume_of_air, atm, volume;
|
|
|
|
|
|
|
|
/* If we have no working pressure, it had *better* be just a physical size! */
|
|
|
|
if (!type->workingpressure.mbar)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* No size either? Nothing to go on */
|
|
|
|
if (!type->size.mliter)
|
|
|
|
return;
|
|
|
|
|
2013-01-11 01:26:10 +00:00
|
|
|
if (xml_parsing_units.volume == CUFT) {
|
2011-12-30 21:09:17 +00:00
|
|
|
/* confusing - we don't really start from ml but millicuft !*/
|
|
|
|
volume_of_air = cuft_to_l(type->size.mliter);
|
|
|
|
atm = to_ATM(type->workingpressure); /* working pressure in atm */
|
|
|
|
volume = volume_of_air / atm; /* milliliters at 1 atm: "true size" */
|
|
|
|
type->size.mliter = volume + 0.5;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Ok, we have both size and pressure: try to match a description */
|
|
|
|
match_standard_cylinder(type);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sanitize_cylinder_info(struct dive *dive)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
sanitize_gasmix(&dive->cylinder[i].gasmix);
|
|
|
|
sanitize_cylinder_type(&dive->cylinder[i].type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-10 10:40:35 +00:00
|
|
|
/* some events should never be thrown away */
|
|
|
|
static gboolean is_potentially_redundant(struct event *event)
|
|
|
|
{
|
|
|
|
if (!strcmp(event->name, "gaschange"))
|
|
|
|
return FALSE;
|
2012-11-10 19:02:21 +00:00
|
|
|
if (!strcmp(event->name, "bookmark"))
|
|
|
|
return FALSE;
|
|
|
|
if (!strcmp(event->name, "heading"))
|
|
|
|
return FALSE;
|
2012-11-10 10:40:35 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* match just by name - we compare the details in the code that uses this helper */
|
2012-11-24 02:51:27 +00:00
|
|
|
static struct event *find_previous_event(struct divecomputer *dc, struct event *event)
|
2012-11-10 10:40:35 +00:00
|
|
|
{
|
2012-11-24 02:51:27 +00:00
|
|
|
struct event *ev = dc->events;
|
2012-11-10 10:40:35 +00:00
|
|
|
struct event *previous = NULL;
|
|
|
|
|
|
|
|
if (!event->name)
|
|
|
|
return NULL;
|
|
|
|
while (ev && ev != event) {
|
2013-01-29 21:10:46 +00:00
|
|
|
if (ev->name && !strcmp(ev->name, event->name))
|
2012-11-10 10:40:35 +00:00
|
|
|
previous = ev;
|
|
|
|
ev = ev->next;
|
|
|
|
}
|
|
|
|
return previous;
|
|
|
|
}
|
|
|
|
|
2013-02-08 06:48:07 +00:00
|
|
|
/* right now this only operates on the first divecomputer */
|
2011-09-03 20:19:26 +00:00
|
|
|
struct dive *fixup_dive(struct dive *dive)
|
|
|
|
{
|
2011-11-19 12:09:14 +00:00
|
|
|
int i,j;
|
2011-09-03 20:19:26 +00:00
|
|
|
double depthtime = 0;
|
|
|
|
int lasttime = 0;
|
2011-11-17 14:03:11 +00:00
|
|
|
int lastindex = -1;
|
2011-09-03 20:19:26 +00:00
|
|
|
int start = -1, end = -1;
|
|
|
|
int maxdepth = 0, mintemp = 0;
|
|
|
|
int lastdepth = 0;
|
2011-11-17 14:03:11 +00:00
|
|
|
int lasttemp = 0, lastpressure = 0;
|
2011-11-19 12:09:14 +00:00
|
|
|
int pressure_delta[MAX_CYLINDERS] = {INT_MAX, };
|
2012-11-24 02:51:27 +00:00
|
|
|
struct divecomputer *dc;
|
2012-11-10 10:40:35 +00:00
|
|
|
struct event *event;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2012-10-02 14:56:12 +00:00
|
|
|
add_people(dive->buddy);
|
|
|
|
add_people(dive->divemaster);
|
|
|
|
add_location(dive->location);
|
|
|
|
add_suit(dive->suit);
|
2011-12-30 21:09:17 +00:00
|
|
|
sanitize_cylinder_info(dive);
|
2012-12-11 05:18:48 +00:00
|
|
|
dive->maxcns = dive->cns;
|
2012-11-24 02:51:27 +00:00
|
|
|
dc = &dive->dc;
|
|
|
|
for (i = 0; i < dc->samples; i++) {
|
|
|
|
struct sample *sample = dc->sample + i;
|
2011-09-03 20:19:26 +00:00
|
|
|
int time = sample->time.seconds;
|
|
|
|
int depth = sample->depth.mm;
|
|
|
|
int temp = sample->temperature.mkelvin;
|
2011-11-17 14:03:11 +00:00
|
|
|
int pressure = sample->cylinderpressure.mbar;
|
First step in cleaning up cylinder pressure sensor logic
This clarifies/changes the meaning of our "cylinderindex" entry in our
samples. It has been rather confused, because different dive computers
have done things differently, and the naming really hasn't helped.
There are two totally different - and independent - cylinder "indexes":
- the pressure sensor index, which indicates which cylinder the sensor
data is from.
- the "active cylinder" index, which indicates which cylinder we actually
breathe from.
These two values really are totally independent, and have nothing
what-so-ever to do with each other. The sensor index may well be fixed:
many dive computers only support a single pressure sensor (whether
wireless or wired), and the sensor index is thus always zero.
Other dive computers may support multiple pressure sensors, and the gas
switch event may - or may not - indicate that the sensor changed too. A
dive computer might give the sensor data for *all* cylinders it can read,
regardless of which one is the one we're actively breathing. In fact, some
dive computers might give sensor data for not just *your* cylinder, but
your buddies.
This patch renames "cylinderindex" in the samples as "sensor", making it
quite clear that it's about which sensor index the pressure data in the
sample is about.
The way we figure out which is the currently active gas is with an
explicit has change event. If a computer (like the Uemis Zurich) joins the
two concepts together, then a sensor change should also create a gas
switch event. This patch also changes the Uemis importer to do that.
Finally, it should be noted that the plot info works totally separately
from the sample data, and is about what we actually *display*, not about
the sample pressures etc. In the plot info, the "cylinderindex" does in
fact mean the currently active cylinder, and while it is initially set to
match the sensor information from the samples, we then walk the gas change
events and fix it up - and if the active cylinder differs from the sensor
cylinder, we clear the sensor data.
[Dirk Hohndel: this conflicted with some of my recent changes - I think
I merged things correctly...]
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
|
|
|
int index = sample->sensor;
|
2011-11-17 14:03:11 +00:00
|
|
|
|
2011-11-19 12:09:14 +00:00
|
|
|
if (index == lastindex) {
|
|
|
|
/* Remove duplicate redundant pressure information */
|
|
|
|
if (pressure == lastpressure)
|
|
|
|
sample->cylinderpressure.mbar = 0;
|
|
|
|
/* check for simply linear data in the samples
|
|
|
|
+INT_MAX means uninitialized, -INT_MAX means not linear */
|
|
|
|
if (pressure_delta[index] != -INT_MAX && lastpressure) {
|
|
|
|
if (pressure_delta[index] == INT_MAX) {
|
|
|
|
pressure_delta[index] = abs(pressure - lastpressure);
|
|
|
|
} else {
|
|
|
|
int cur_delta = abs(pressure - lastpressure);
|
|
|
|
if (cur_delta && abs(cur_delta - pressure_delta[index]) > 150) {
|
|
|
|
/* ok the samples aren't just a linearisation
|
|
|
|
* between start and end */
|
|
|
|
pressure_delta[index] = -INT_MAX;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-11-17 14:03:11 +00:00
|
|
|
lastindex = index;
|
|
|
|
lastpressure = pressure;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2012-11-11 09:36:46 +00:00
|
|
|
if (lastdepth > SURFACE_THRESHOLD)
|
2011-09-03 20:19:26 +00:00
|
|
|
end = time;
|
|
|
|
|
2012-11-11 09:36:46 +00:00
|
|
|
if (depth > SURFACE_THRESHOLD) {
|
2011-09-03 20:19:26 +00:00
|
|
|
if (start < 0)
|
|
|
|
start = lasttime;
|
|
|
|
if (depth > maxdepth)
|
|
|
|
maxdepth = depth;
|
|
|
|
}
|
2011-09-05 16:12:54 +00:00
|
|
|
|
2011-11-09 15:37:25 +00:00
|
|
|
fixup_pressure(dive, sample);
|
2011-09-05 16:12:54 +00:00
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
if (temp) {
|
2011-09-04 18:20:27 +00:00
|
|
|
/*
|
|
|
|
* If we have consecutive identical
|
|
|
|
* temperature readings, throw away
|
2011-09-13 03:37:32 +00:00
|
|
|
* the redundant ones.
|
2011-09-04 18:20:27 +00:00
|
|
|
*/
|
2011-09-13 03:37:32 +00:00
|
|
|
if (lasttemp == temp)
|
|
|
|
sample->temperature.mkelvin = 0;
|
|
|
|
else
|
2011-09-04 18:20:27 +00:00
|
|
|
lasttemp = temp;
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
if (!mintemp || temp < mintemp)
|
|
|
|
mintemp = temp;
|
|
|
|
}
|
2013-01-24 18:58:59 +00:00
|
|
|
update_min_max_temperatures(dive, sample);
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
depthtime += (time - lasttime) * (lastdepth + depth) / 2;
|
|
|
|
lastdepth = depth;
|
|
|
|
lasttime = time;
|
2012-12-11 05:18:48 +00:00
|
|
|
if (sample->cns > dive->maxcns)
|
|
|
|
dive->maxcns = sample->cns;
|
2011-09-03 20:19:26 +00:00
|
|
|
}
|
2013-02-04 05:21:33 +00:00
|
|
|
|
2011-11-19 12:09:14 +00:00
|
|
|
/* if all the samples for a cylinder have pressure data that
|
|
|
|
* is basically equidistant throw out the sample cylinder pressure
|
|
|
|
* information but make sure we still have a valid start and end
|
|
|
|
* pressure
|
|
|
|
* this happens when DivingLog decides to linearalize the
|
|
|
|
* pressure between beginning and end and for strange reasons
|
|
|
|
* decides to put that in the sample data as if it came from
|
|
|
|
* the dive computer; we don't want that (we'll visualize with
|
|
|
|
* constant SAC rate instead)
|
|
|
|
* WARNING WARNING - I have only seen this in single tank dives
|
|
|
|
* --- maybe I should try to create a multi tank dive and see what
|
|
|
|
* --- divinglog does there - but the code right now is only tested
|
|
|
|
* --- for the single tank case */
|
|
|
|
for (j = 0; j < MAX_CYLINDERS; j++) {
|
|
|
|
if (abs(pressure_delta[j]) != INT_MAX) {
|
|
|
|
cylinder_t *cyl = dive->cylinder + j;
|
2012-11-24 02:51:27 +00:00
|
|
|
for (i = 0; i < dc->samples; i++)
|
First step in cleaning up cylinder pressure sensor logic
This clarifies/changes the meaning of our "cylinderindex" entry in our
samples. It has been rather confused, because different dive computers
have done things differently, and the naming really hasn't helped.
There are two totally different - and independent - cylinder "indexes":
- the pressure sensor index, which indicates which cylinder the sensor
data is from.
- the "active cylinder" index, which indicates which cylinder we actually
breathe from.
These two values really are totally independent, and have nothing
what-so-ever to do with each other. The sensor index may well be fixed:
many dive computers only support a single pressure sensor (whether
wireless or wired), and the sensor index is thus always zero.
Other dive computers may support multiple pressure sensors, and the gas
switch event may - or may not - indicate that the sensor changed too. A
dive computer might give the sensor data for *all* cylinders it can read,
regardless of which one is the one we're actively breathing. In fact, some
dive computers might give sensor data for not just *your* cylinder, but
your buddies.
This patch renames "cylinderindex" in the samples as "sensor", making it
quite clear that it's about which sensor index the pressure data in the
sample is about.
The way we figure out which is the currently active gas is with an
explicit has change event. If a computer (like the Uemis Zurich) joins the
two concepts together, then a sensor change should also create a gas
switch event. This patch also changes the Uemis importer to do that.
Finally, it should be noted that the plot info works totally separately
from the sample data, and is about what we actually *display*, not about
the sample pressures etc. In the plot info, the "cylinderindex" does in
fact mean the currently active cylinder, and while it is initially set to
match the sensor information from the samples, we then walk the gas change
events and fix it up - and if the active cylinder differs from the sensor
cylinder, we clear the sensor data.
[Dirk Hohndel: this conflicted with some of my recent changes - I think
I merged things correctly...]
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
|
|
|
if (dc->sample[i].sensor == j)
|
2012-11-24 02:51:27 +00:00
|
|
|
dc->sample[i].cylinderpressure.mbar = 0;
|
2011-11-19 12:09:14 +00:00
|
|
|
if (! cyl->start.mbar)
|
|
|
|
cyl->start.mbar = cyl->sample_start.mbar;
|
|
|
|
if (! cyl->end.mbar)
|
|
|
|
cyl->end.mbar = cyl->sample_end.mbar;
|
|
|
|
cyl->sample_start.mbar = 0;
|
|
|
|
cyl->sample_end.mbar = 0;
|
|
|
|
}
|
|
|
|
}
|
2012-08-18 16:02:27 +00:00
|
|
|
if (end < 0) {
|
2012-07-29 09:52:51 +00:00
|
|
|
/* Assume an ascent/descent rate of 9 m/min */
|
2013-01-23 18:25:31 +00:00
|
|
|
int depth = dive->dc.maxdepth.mm;
|
2012-08-18 16:02:27 +00:00
|
|
|
int asc_desc_time = depth*60/9000;
|
2013-01-23 18:25:31 +00:00
|
|
|
int duration = dive->dc.duration.seconds;
|
2012-08-18 16:02:27 +00:00
|
|
|
|
2012-08-29 16:10:26 +00:00
|
|
|
/* Some sanity checks against insane dives */
|
|
|
|
if (duration < 2)
|
2012-08-18 16:02:27 +00:00
|
|
|
duration = 2;
|
2012-08-29 16:10:26 +00:00
|
|
|
if (asc_desc_time * 2 >= duration)
|
|
|
|
asc_desc_time = duration/2;
|
|
|
|
|
2013-01-23 18:25:31 +00:00
|
|
|
dive->dc.meandepth.mm = depth*(duration-asc_desc_time)/duration;
|
2011-09-03 20:19:26 +00:00
|
|
|
return dive;
|
2012-07-29 09:52:51 +00:00
|
|
|
}
|
2011-09-04 20:06:47 +00:00
|
|
|
|
2013-01-23 18:25:31 +00:00
|
|
|
update_duration(&dive->dc.duration, end - start);
|
2011-09-03 20:19:26 +00:00
|
|
|
if (start != end)
|
2011-09-04 20:06:47 +00:00
|
|
|
depthtime /= (end - start);
|
|
|
|
|
2013-01-23 18:25:31 +00:00
|
|
|
update_depth(&dive->dc.meandepth, depthtime);
|
|
|
|
update_temperature(&dive->dc.watertemp, mintemp);
|
|
|
|
update_depth(&dive->dc.maxdepth, maxdepth);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2011-10-22 15:12:30 +00:00
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
2011-11-09 15:37:25 +00:00
|
|
|
cylinder_t *cyl = dive->cylinder + i;
|
|
|
|
add_cylinder_description(&cyl->type);
|
2011-11-10 23:33:38 +00:00
|
|
|
if (same_rounded_pressure(cyl->sample_start, cyl->start))
|
2011-11-09 15:37:25 +00:00
|
|
|
cyl->start.mbar = 0;
|
2011-11-10 23:33:38 +00:00
|
|
|
if (same_rounded_pressure(cyl->sample_end, cyl->end))
|
2011-11-09 15:37:25 +00:00
|
|
|
cyl->end.mbar = 0;
|
2011-10-22 15:12:30 +00:00
|
|
|
}
|
2012-08-06 21:03:24 +00:00
|
|
|
for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) {
|
|
|
|
weightsystem_t *ws = dive->weightsystem + i;
|
|
|
|
add_weightsystem_description(ws);
|
|
|
|
}
|
2011-10-22 15:12:30 +00:00
|
|
|
|
2012-11-10 10:40:35 +00:00
|
|
|
/* events are stored as a linked list, so the concept of
|
|
|
|
* "consecutive, identical events" is somewhat hard to
|
|
|
|
* implement correctly (especially given that on some dive
|
|
|
|
* computers events are asynchronous, so they can come in
|
|
|
|
* between what would be the non-constant sample rate).
|
|
|
|
*
|
|
|
|
* So what we do is that we throw away clearly redundant
|
|
|
|
* events that are fewer than 61 seconds apart (assuming there
|
|
|
|
* is no dive computer with a sample rate of more than 60
|
|
|
|
* seconds... that would be pretty pointless to plot the
|
|
|
|
* profile with)
|
|
|
|
* We first only mark the events for deletion so that we
|
|
|
|
* still know when the previous event happened. */
|
2012-11-24 02:51:27 +00:00
|
|
|
event = dc->events;
|
2012-11-10 10:40:35 +00:00
|
|
|
while (event) {
|
|
|
|
struct event *prev;
|
|
|
|
if (is_potentially_redundant(event)) {
|
2012-11-24 02:51:27 +00:00
|
|
|
prev = find_previous_event(dc, event);
|
2012-11-10 10:40:35 +00:00
|
|
|
if (prev && prev->value == event->value &&
|
|
|
|
prev->flags == event->flags &&
|
|
|
|
event->time.seconds - prev->time.seconds < 61)
|
|
|
|
event->deleted = TRUE;
|
|
|
|
}
|
|
|
|
event = event->next;
|
|
|
|
}
|
2012-11-24 02:51:27 +00:00
|
|
|
event = dc->events;
|
2012-11-10 10:40:35 +00:00
|
|
|
while (event) {
|
|
|
|
if (event->next && event->next->deleted) {
|
|
|
|
struct event *nextnext = event->next->next;
|
|
|
|
free(event->next);
|
|
|
|
event->next = nextnext;
|
|
|
|
} else {
|
|
|
|
event = event->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
return dive;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Don't pick a zero for MERGE_MIN() */
|
|
|
|
#define MERGE_MAX(res, a, b, n) res->n = MAX(a->n, b->n)
|
|
|
|
#define MERGE_MIN(res, a, b, n) res->n = (a->n)?(b->n)?MIN(a->n, b->n):(a->n):(b->n)
|
2011-09-23 01:01:01 +00:00
|
|
|
#define MERGE_TXT(res, a, b, n) res->n = merge_text(a->n, b->n)
|
2011-09-23 03:50:07 +00:00
|
|
|
#define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
static struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc)
|
2011-09-03 20:19:26 +00:00
|
|
|
{
|
2012-11-24 02:51:27 +00:00
|
|
|
struct sample *p = prepare_sample(dc);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2012-11-24 02:05:38 +00:00
|
|
|
if (p) {
|
|
|
|
*p = *sample;
|
|
|
|
p->time.seconds = time;
|
2012-11-24 02:51:27 +00:00
|
|
|
finish_sample(dc);
|
2012-11-24 02:05:38 +00:00
|
|
|
}
|
|
|
|
return p;
|
2011-09-03 20:19:26 +00:00
|
|
|
}
|
|
|
|
|
2012-11-17 21:20:10 +00:00
|
|
|
/*
|
|
|
|
* This is like add_sample(), but if the distance from the last sample
|
|
|
|
* is excessive, we add two surface samples in between.
|
|
|
|
*
|
|
|
|
* This is so that if you merge two non-overlapping dives, we make sure
|
|
|
|
* that the time in between the dives is at the surface, not some "last
|
|
|
|
* sample that happened to be at a depth of 1.2m".
|
|
|
|
*/
|
2012-11-24 02:51:27 +00:00
|
|
|
static void merge_one_sample(struct sample *sample, int time, struct divecomputer *dc)
|
2012-11-17 21:20:10 +00:00
|
|
|
{
|
2012-11-24 02:51:27 +00:00
|
|
|
int last = dc->samples-1;
|
2012-11-17 21:20:10 +00:00
|
|
|
if (last >= 0) {
|
|
|
|
static struct sample surface;
|
2012-12-07 17:42:47 +00:00
|
|
|
struct sample *prev = dc->sample + last;
|
|
|
|
int last_time = prev->time.seconds;
|
|
|
|
int last_depth = prev->depth.mm;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Only do surface events if the samples are more than
|
|
|
|
* a minute apart, and shallower than 5m
|
|
|
|
*/
|
|
|
|
if (time > last_time + 60 && last_depth < 5000) {
|
2012-11-24 02:51:27 +00:00
|
|
|
add_sample(&surface, last_time+20, dc);
|
|
|
|
add_sample(&surface, time - 20, dc);
|
2012-11-17 21:20:10 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-24 02:51:27 +00:00
|
|
|
add_sample(sample, time, dc);
|
2012-11-17 21:20:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
/*
|
|
|
|
* Merge samples. Dive 'a' is "offset" seconds before Dive 'b'
|
|
|
|
*/
|
2012-11-24 02:51:27 +00:00
|
|
|
static void merge_samples(struct divecomputer *res, struct divecomputer *a, struct divecomputer *b, int offset)
|
2011-09-03 20:19:26 +00:00
|
|
|
{
|
|
|
|
int asamples = a->samples;
|
|
|
|
int bsamples = b->samples;
|
|
|
|
struct sample *as = a->sample;
|
|
|
|
struct sample *bs = b->sample;
|
|
|
|
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
/*
|
|
|
|
* We want a positive sample offset, so that sample
|
|
|
|
* times are always positive. So if the samples for
|
|
|
|
* 'b' are before the samples for 'a' (so the offset
|
|
|
|
* is negative), we switch a and b around, and use
|
|
|
|
* the reverse offset.
|
|
|
|
*/
|
|
|
|
if (offset < 0) {
|
|
|
|
offset = -offset;
|
|
|
|
asamples = bsamples;
|
|
|
|
bsamples = a->samples;
|
|
|
|
as = bs;
|
|
|
|
bs = a->sample;
|
|
|
|
}
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
for (;;) {
|
|
|
|
int at, bt;
|
|
|
|
struct sample sample;
|
|
|
|
|
|
|
|
if (!res)
|
2012-11-24 02:51:27 +00:00
|
|
|
return;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
at = asamples ? as->time.seconds : -1;
|
|
|
|
bt = bsamples ? bs->time.seconds + offset : -1;
|
|
|
|
|
|
|
|
/* No samples? All done! */
|
|
|
|
if (at < 0 && bt < 0)
|
2012-11-24 02:51:27 +00:00
|
|
|
return;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
/* Only samples from a? */
|
|
|
|
if (bt < 0) {
|
|
|
|
add_sample_a:
|
2012-11-24 02:05:38 +00:00
|
|
|
merge_one_sample(as, at, res);
|
2011-09-03 20:19:26 +00:00
|
|
|
as++;
|
|
|
|
asamples--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only samples from b? */
|
|
|
|
if (at < 0) {
|
|
|
|
add_sample_b:
|
2012-11-24 02:05:38 +00:00
|
|
|
merge_one_sample(bs, bt, res);
|
2011-09-03 20:19:26 +00:00
|
|
|
bs++;
|
|
|
|
bsamples--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (at < bt)
|
|
|
|
goto add_sample_a;
|
|
|
|
if (at > bt)
|
|
|
|
goto add_sample_b;
|
|
|
|
|
|
|
|
/* same-time sample: add a merged sample. Take the non-zero ones */
|
|
|
|
sample = *bs;
|
|
|
|
if (as->depth.mm)
|
|
|
|
sample.depth = as->depth;
|
|
|
|
if (as->temperature.mkelvin)
|
|
|
|
sample.temperature = as->temperature;
|
2011-09-04 03:31:18 +00:00
|
|
|
if (as->cylinderpressure.mbar)
|
|
|
|
sample.cylinderpressure = as->cylinderpressure;
|
First step in cleaning up cylinder pressure sensor logic
This clarifies/changes the meaning of our "cylinderindex" entry in our
samples. It has been rather confused, because different dive computers
have done things differently, and the naming really hasn't helped.
There are two totally different - and independent - cylinder "indexes":
- the pressure sensor index, which indicates which cylinder the sensor
data is from.
- the "active cylinder" index, which indicates which cylinder we actually
breathe from.
These two values really are totally independent, and have nothing
what-so-ever to do with each other. The sensor index may well be fixed:
many dive computers only support a single pressure sensor (whether
wireless or wired), and the sensor index is thus always zero.
Other dive computers may support multiple pressure sensors, and the gas
switch event may - or may not - indicate that the sensor changed too. A
dive computer might give the sensor data for *all* cylinders it can read,
regardless of which one is the one we're actively breathing. In fact, some
dive computers might give sensor data for not just *your* cylinder, but
your buddies.
This patch renames "cylinderindex" in the samples as "sensor", making it
quite clear that it's about which sensor index the pressure data in the
sample is about.
The way we figure out which is the currently active gas is with an
explicit has change event. If a computer (like the Uemis Zurich) joins the
two concepts together, then a sensor change should also create a gas
switch event. This patch also changes the Uemis importer to do that.
Finally, it should be noted that the plot info works totally separately
from the sample data, and is about what we actually *display*, not about
the sample pressures etc. In the plot info, the "cylinderindex" does in
fact mean the currently active cylinder, and while it is initially set to
match the sensor information from the samples, we then walk the gas change
events and fix it up - and if the active cylinder differs from the sensor
cylinder, we clear the sensor data.
[Dirk Hohndel: this conflicted with some of my recent changes - I think
I merged things correctly...]
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
|
|
|
if (as->sensor)
|
|
|
|
sample.sensor = as->sensor;
|
2012-12-08 04:08:29 +00:00
|
|
|
if (as->cns)
|
|
|
|
sample.cns = as->cns;
|
|
|
|
if (as->po2)
|
|
|
|
sample.po2 = as->po2;
|
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)
|
|
|
|
sample.in_deco = TRUE;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2012-11-24 02:05:38 +00:00
|
|
|
merge_one_sample(&sample, at, res);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
as++;
|
|
|
|
bs++;
|
|
|
|
asamples--;
|
|
|
|
bsamples--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *merge_text(const char *a, const char *b)
|
|
|
|
{
|
|
|
|
char *res;
|
Small changes in the memory management when dive-merging
This patch makes a couple of modifications:
1) divelist.c:delete_single_dive() now tries to free all memory associated
with a dive, such as the string values for divemaster, location, notes &
etc.
2) dive.c:merge_text(), now always makes a copy in memory for the returned
string - either combined or one of the two which are passed
to the function.
The reason for the above two changes is that when (say) importing the same
data over and over, technically a merge will occur for the contained dives,
but mapped pointers can go out of scope.
main.c:report_dives() calls try_to_merge() and if succeeds the two dives
that were merged are deleted from the table. when we delete a dive,
we now make sure all string data is cleared with it, but also in the actual merge
itself, which precedes, copies of the merged texts are made (with merge_text()),
so that the new, resulted dive has his own text allocations.
Signed-off-by: Lubomir I. Ivanov <neolit123@gmail.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-24 01:53:25 +00:00
|
|
|
if (!a && !b)
|
|
|
|
return NULL;
|
2011-09-03 20:19:26 +00:00
|
|
|
if (!a || !*a)
|
Small changes in the memory management when dive-merging
This patch makes a couple of modifications:
1) divelist.c:delete_single_dive() now tries to free all memory associated
with a dive, such as the string values for divemaster, location, notes &
etc.
2) dive.c:merge_text(), now always makes a copy in memory for the returned
string - either combined or one of the two which are passed
to the function.
The reason for the above two changes is that when (say) importing the same
data over and over, technically a merge will occur for the contained dives,
but mapped pointers can go out of scope.
main.c:report_dives() calls try_to_merge() and if succeeds the two dives
that were merged are deleted from the table. when we delete a dive,
we now make sure all string data is cleared with it, but also in the actual merge
itself, which precedes, copies of the merged texts are made (with merge_text()),
so that the new, resulted dive has his own text allocations.
Signed-off-by: Lubomir I. Ivanov <neolit123@gmail.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-24 01:53:25 +00:00
|
|
|
return strdup(b);
|
2011-09-03 20:19:26 +00:00
|
|
|
if (!b || !*b)
|
Small changes in the memory management when dive-merging
This patch makes a couple of modifications:
1) divelist.c:delete_single_dive() now tries to free all memory associated
with a dive, such as the string values for divemaster, location, notes &
etc.
2) dive.c:merge_text(), now always makes a copy in memory for the returned
string - either combined or one of the two which are passed
to the function.
The reason for the above two changes is that when (say) importing the same
data over and over, technically a merge will occur for the contained dives,
but mapped pointers can go out of scope.
main.c:report_dives() calls try_to_merge() and if succeeds the two dives
that were merged are deleted from the table. when we delete a dive,
we now make sure all string data is cleared with it, but also in the actual merge
itself, which precedes, copies of the merged texts are made (with merge_text()),
so that the new, resulted dive has his own text allocations.
Signed-off-by: Lubomir I. Ivanov <neolit123@gmail.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-24 01:53:25 +00:00
|
|
|
return strdup(a);
|
2011-09-03 20:19:26 +00:00
|
|
|
if (!strcmp(a,b))
|
Small changes in the memory management when dive-merging
This patch makes a couple of modifications:
1) divelist.c:delete_single_dive() now tries to free all memory associated
with a dive, such as the string values for divemaster, location, notes &
etc.
2) dive.c:merge_text(), now always makes a copy in memory for the returned
string - either combined or one of the two which are passed
to the function.
The reason for the above two changes is that when (say) importing the same
data over and over, technically a merge will occur for the contained dives,
but mapped pointers can go out of scope.
main.c:report_dives() calls try_to_merge() and if succeeds the two dives
that were merged are deleted from the table. when we delete a dive,
we now make sure all string data is cleared with it, but also in the actual merge
itself, which precedes, copies of the merged texts are made (with merge_text()),
so that the new, resulted dive has his own text allocations.
Signed-off-by: Lubomir I. Ivanov <neolit123@gmail.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-24 01:53:25 +00:00
|
|
|
return strdup(a);
|
2012-12-28 13:43:04 +00:00
|
|
|
res = malloc(strlen(a) + strlen(b) + 32);
|
2011-09-03 20:19:26 +00:00
|
|
|
if (!res)
|
|
|
|
return (char *)a;
|
2012-10-11 00:42:59 +00:00
|
|
|
sprintf(res, _("(%s) or (%s)"), a, b);
|
2011-09-03 20:19:26 +00:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2011-09-23 03:28:04 +00:00
|
|
|
#define SORT(a,b,field) \
|
|
|
|
if (a->field != b->field) return a->field < b->field ? -1 : 1
|
|
|
|
|
|
|
|
static int sort_event(struct event *a, struct event *b)
|
|
|
|
{
|
|
|
|
SORT(a,b,time.seconds);
|
|
|
|
SORT(a,b,type);
|
|
|
|
SORT(a,b,flags);
|
|
|
|
SORT(a,b,value);
|
|
|
|
return strcmp(a->name, b->name);
|
|
|
|
}
|
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
static void merge_events(struct divecomputer *res, struct divecomputer *src1, struct divecomputer *src2, int offset)
|
2011-09-23 03:28:04 +00:00
|
|
|
{
|
|
|
|
struct event *a, *b;
|
|
|
|
struct event **p = &res->events;
|
|
|
|
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
/* Always use positive offsets */
|
|
|
|
if (offset < 0) {
|
2012-11-24 02:51:27 +00:00
|
|
|
struct divecomputer *tmp;
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
|
|
|
offset = -offset;
|
|
|
|
tmp = src1;
|
|
|
|
src1 = src2;
|
|
|
|
src2 = tmp;
|
|
|
|
}
|
|
|
|
|
2011-09-23 03:28:04 +00:00
|
|
|
a = src1->events;
|
|
|
|
b = src2->events;
|
|
|
|
while (b) {
|
|
|
|
b->time.seconds += offset;
|
|
|
|
b = b->next;
|
|
|
|
}
|
|
|
|
b = src2->events;
|
|
|
|
|
|
|
|
while (a || b) {
|
|
|
|
int s;
|
|
|
|
if (!b) {
|
|
|
|
*p = a;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!a) {
|
|
|
|
*p = b;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
s = sort_event(a, b);
|
|
|
|
/* Pick b */
|
|
|
|
if (s > 0) {
|
|
|
|
*p = b;
|
|
|
|
p = &b->next;
|
|
|
|
b = b->next;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Pick 'a' or neither */
|
|
|
|
if (s < 0) {
|
|
|
|
*p = a;
|
|
|
|
p = &a->next;
|
|
|
|
}
|
|
|
|
a = a->next;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-04 03:31:18 +00:00
|
|
|
/* Pick whichever has any info (if either). Prefer 'a' */
|
|
|
|
static void merge_cylinder_type(cylinder_type_t *res, cylinder_type_t *a, cylinder_type_t *b)
|
|
|
|
{
|
2012-12-24 01:53:28 +00:00
|
|
|
cylinder_type_t *clean = a;
|
|
|
|
if (a->size.mliter) {
|
|
|
|
clean = b;
|
2011-09-04 03:31:18 +00:00
|
|
|
b = a;
|
2012-12-24 01:53:28 +00:00
|
|
|
}
|
|
|
|
if (clean->description)
|
|
|
|
free((void *)clean->description);
|
2011-09-04 03:31:18 +00:00
|
|
|
*res = *b;
|
|
|
|
}
|
|
|
|
|
2011-09-12 16:47:55 +00:00
|
|
|
static void merge_cylinder_mix(struct gasmix *res, struct gasmix *a, struct gasmix *b)
|
2011-09-04 03:31:18 +00:00
|
|
|
{
|
|
|
|
if (a->o2.permille)
|
|
|
|
b = a;
|
|
|
|
*res = *b;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void merge_cylinder_info(cylinder_t *res, cylinder_t *a, cylinder_t *b)
|
|
|
|
{
|
|
|
|
merge_cylinder_type(&res->type, &a->type, &b->type);
|
|
|
|
merge_cylinder_mix(&res->gasmix, &a->gasmix, &b->gasmix);
|
2011-09-05 16:12:54 +00:00
|
|
|
MERGE_MAX(res, a, b, start.mbar);
|
|
|
|
MERGE_MIN(res, a, b, end.mbar);
|
2011-09-04 03:31:18 +00:00
|
|
|
}
|
|
|
|
|
2012-10-29 18:27:14 +00:00
|
|
|
static void merge_weightsystem_info(weightsystem_t *res, weightsystem_t *a, weightsystem_t *b)
|
|
|
|
{
|
|
|
|
if (!a->weight.grams)
|
|
|
|
a = b;
|
|
|
|
*res = *a;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void merge_equipment(struct dive *res, struct dive *a, struct dive *b)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++)
|
|
|
|
merge_cylinder_info(res->cylinder+i, a->cylinder + i, b->cylinder + i);
|
|
|
|
for (i = 0; i < MAX_WEIGHTSYSTEMS; i++)
|
|
|
|
merge_weightsystem_info(res->weightsystem+i, a->weightsystem + i, b->weightsystem + i);
|
|
|
|
}
|
|
|
|
|
2012-11-09 18:46:39 +00:00
|
|
|
/*
|
|
|
|
* When merging two dives, this picks the trip from one, and removes it
|
|
|
|
* from the other.
|
|
|
|
*
|
|
|
|
* The 'next' dive is not involved in the dive merging, but is the dive
|
|
|
|
* that will be the next dive after the merged dive.
|
|
|
|
*/
|
2012-11-11 06:49:19 +00:00
|
|
|
static void pick_trip(struct dive *res, struct dive *pick)
|
2012-11-09 18:46:39 +00:00
|
|
|
{
|
|
|
|
tripflag_t tripflag = pick->tripflag;
|
|
|
|
dive_trip_t *trip = pick->divetrip;
|
|
|
|
|
|
|
|
res->tripflag = tripflag;
|
2012-11-10 18:51:03 +00:00
|
|
|
add_dive_to_trip(res, trip);
|
2012-11-09 18:46:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Pick a trip for a dive
|
|
|
|
*/
|
2012-11-11 06:20:05 +00:00
|
|
|
static void merge_trip(struct dive *res, struct dive *a, struct dive *b)
|
2012-11-09 18:46:39 +00:00
|
|
|
{
|
2012-11-26 00:51:55 +00:00
|
|
|
dive_trip_t *atrip, *btrip;
|
|
|
|
|
2012-11-09 18:46:39 +00:00
|
|
|
/*
|
|
|
|
* The larger tripflag is more relevant: we prefer
|
|
|
|
* take manually assigned trips over auto-generated
|
|
|
|
* ones.
|
|
|
|
*/
|
|
|
|
if (a->tripflag > b->tripflag)
|
|
|
|
goto pick_a;
|
|
|
|
|
|
|
|
if (a->tripflag < b->tripflag)
|
|
|
|
goto pick_b;
|
|
|
|
|
2012-11-26 00:51:55 +00:00
|
|
|
/* Otherwise, look at the trip data and pick the "better" one */
|
|
|
|
atrip = a->divetrip;
|
|
|
|
btrip = b->divetrip;
|
|
|
|
if (!atrip)
|
|
|
|
goto pick_b;
|
|
|
|
if (!btrip)
|
|
|
|
goto pick_a;
|
|
|
|
if (!atrip->location)
|
2012-11-09 18:46:39 +00:00
|
|
|
goto pick_b;
|
2012-11-26 00:51:55 +00:00
|
|
|
if (!btrip->location)
|
2012-11-09 18:46:39 +00:00
|
|
|
goto pick_a;
|
2012-11-26 00:51:55 +00:00
|
|
|
if (!atrip->notes)
|
2012-11-09 18:46:39 +00:00
|
|
|
goto pick_b;
|
2012-11-26 00:51:55 +00:00
|
|
|
if (!btrip->notes)
|
2012-11-09 18:46:39 +00:00
|
|
|
goto pick_a;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ok, so both have location and notes.
|
|
|
|
* Pick the earlier one.
|
|
|
|
*/
|
|
|
|
if (a->when < b->when)
|
|
|
|
goto pick_a;
|
|
|
|
goto pick_b;
|
|
|
|
|
|
|
|
pick_a:
|
2012-11-11 06:49:19 +00:00
|
|
|
b = a;
|
2012-11-09 18:46:39 +00:00
|
|
|
pick_b:
|
2012-11-11 06:49:19 +00:00
|
|
|
pick_trip(res, b);
|
2012-11-09 18:46:39 +00:00
|
|
|
}
|
|
|
|
|
2012-11-25 23:10:03 +00:00
|
|
|
#if CURRENTLY_NOT_USED
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
/*
|
|
|
|
* Sample 's' is between samples 'a' and 'b'. It is 'offset' seconds before 'b'.
|
|
|
|
*
|
|
|
|
* If 's' and 'a' are at the same time, offset is 0, and b is NULL.
|
|
|
|
*/
|
|
|
|
static int compare_sample(struct sample *s, struct sample *a, struct sample *b, int offset)
|
|
|
|
{
|
|
|
|
unsigned int depth = a->depth.mm;
|
|
|
|
int diff;
|
|
|
|
|
|
|
|
if (offset) {
|
|
|
|
unsigned int interval = b->time.seconds - a->time.seconds;
|
|
|
|
unsigned int depth_a = a->depth.mm;
|
|
|
|
unsigned int depth_b = b->depth.mm;
|
|
|
|
|
|
|
|
if (offset > interval)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* pick the average depth, scaled by the offset from 'b' */
|
|
|
|
depth = (depth_a * offset) + (depth_b * (interval - offset));
|
|
|
|
depth /= interval;
|
|
|
|
}
|
|
|
|
diff = s->depth.mm - depth;
|
|
|
|
if (diff < 0)
|
|
|
|
diff = -diff;
|
|
|
|
/* cut off at one meter difference */
|
|
|
|
if (diff > 1000)
|
|
|
|
diff = 1000;
|
|
|
|
return diff*diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate a "difference" in samples between the two dives, given
|
|
|
|
* the offset in seconds between them. Use this to find the best
|
|
|
|
* match of samples between two different dive computers.
|
|
|
|
*/
|
2012-11-24 02:51:27 +00:00
|
|
|
static unsigned long sample_difference(struct divecomputer *a, struct divecomputer *b, int offset)
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
{
|
|
|
|
int asamples = a->samples;
|
|
|
|
int bsamples = b->samples;
|
|
|
|
struct sample *as = a->sample;
|
|
|
|
struct sample *bs = b->sample;
|
|
|
|
unsigned long error = 0;
|
|
|
|
int start = -1;
|
|
|
|
|
|
|
|
if (!asamples || !bsamples)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* skip the first sample - this way we know can always look at
|
|
|
|
* as/bs[-1] to look at the samples around it in the loop.
|
|
|
|
*/
|
|
|
|
as++; bs++;
|
|
|
|
asamples--;
|
|
|
|
bsamples--;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
int at, bt, diff;
|
|
|
|
|
|
|
|
|
|
|
|
/* If we run out of samples, punt */
|
|
|
|
if (!asamples)
|
|
|
|
return INT_MAX;
|
|
|
|
if (!bsamples)
|
|
|
|
return INT_MAX;
|
|
|
|
|
|
|
|
at = as->time.seconds;
|
|
|
|
bt = bs->time.seconds + offset;
|
|
|
|
|
|
|
|
/* b hasn't started yet? Ignore it */
|
|
|
|
if (bt < 0) {
|
|
|
|
bs++;
|
|
|
|
bsamples--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (at < bt) {
|
|
|
|
diff = compare_sample(as, bs-1, bs, bt - at);
|
|
|
|
as++;
|
|
|
|
asamples--;
|
|
|
|
} else if (at > bt) {
|
|
|
|
diff = compare_sample(bs, as-1, as, at - bt);
|
|
|
|
bs++;
|
|
|
|
bsamples--;
|
|
|
|
} else {
|
|
|
|
diff = compare_sample(as, bs, NULL, 0);
|
|
|
|
as++; bs++;
|
|
|
|
asamples--; bsamples--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalid comparison point? */
|
|
|
|
if (diff < 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (start < 0)
|
|
|
|
start = at;
|
|
|
|
|
|
|
|
error += diff;
|
|
|
|
|
|
|
|
if (at - start > 120)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Dive 'a' is 'offset' seconds before dive 'b'
|
|
|
|
*
|
|
|
|
* This is *not* because the dive computers clocks aren't in sync,
|
|
|
|
* it is because the dive computers may "start" the dive at different
|
|
|
|
* points in the dive, so the sample at time X in dive 'a' is the
|
|
|
|
* same as the sample at time X+offset in dive 'b'.
|
|
|
|
*
|
|
|
|
* For example, some dive computers take longer to "wake up" when
|
|
|
|
* they sense that you are under water (ie Uemis Zurich if it was off
|
|
|
|
* when the dive started). And other dive computers have different
|
|
|
|
* depths that they activate at, etc etc.
|
|
|
|
*
|
|
|
|
* If we cannot find a shared offset, don't try to merge.
|
|
|
|
*/
|
2012-11-24 02:51:27 +00:00
|
|
|
static int find_sample_offset(struct divecomputer *a, struct divecomputer *b)
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
{
|
|
|
|
int offset, best;
|
|
|
|
unsigned long max;
|
|
|
|
|
|
|
|
/* No samples? Merge at any time (0 offset) */
|
|
|
|
if (!a->samples)
|
|
|
|
return 0;
|
|
|
|
if (!b->samples)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Common special-case: merging a dive that came from
|
|
|
|
* the same dive computer, so the samples are identical.
|
|
|
|
* Check this first, without wasting time trying to find
|
|
|
|
* some minimal offset case.
|
|
|
|
*/
|
|
|
|
best = 0;
|
|
|
|
max = sample_difference(a, b, 0);
|
|
|
|
if (!max)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Otherwise, look if we can find anything better within
|
|
|
|
* a thirty second window..
|
|
|
|
*/
|
|
|
|
for (offset = -30; offset <= 30; offset++) {
|
|
|
|
unsigned long diff;
|
|
|
|
|
|
|
|
diff = sample_difference(a, b, offset);
|
|
|
|
if (diff > max)
|
|
|
|
continue;
|
|
|
|
best = offset;
|
|
|
|
max = diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
return best;
|
|
|
|
}
|
2012-11-25 23:10:03 +00:00
|
|
|
#endif
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
/*
|
|
|
|
* Are a and b "similar" values, when given a reasonable lower end expected
|
|
|
|
* difference?
|
|
|
|
*
|
|
|
|
* So for example, we'd expect different dive computers to give different
|
|
|
|
* max depth readings. You might have them on different arms, and they
|
|
|
|
* 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)
|
|
|
|
{
|
|
|
|
if (a && b) {
|
|
|
|
unsigned long min, max, diff;
|
|
|
|
|
|
|
|
min = a; max = b;
|
|
|
|
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 */
|
|
|
|
if (diff*10 < max)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-16 18:55:58 +00:00
|
|
|
/*
|
|
|
|
* Match two dive computer entries against each other, and
|
|
|
|
* tell if it's the same dive. Return 0 if "don't know",
|
|
|
|
* positive for "same dive" and negative for "definitely
|
|
|
|
* not the same dive"
|
|
|
|
*/
|
|
|
|
int match_one_dc(struct divecomputer *a, struct divecomputer *b)
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
{
|
2012-12-16 18:55:58 +00:00
|
|
|
/* Not same model? Don't know if matching.. */
|
|
|
|
if (!a->model || !b->model)
|
|
|
|
return 0;
|
|
|
|
if (strcasecmp(a->model, b->model))
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
return 0;
|
2012-12-16 18:55:58 +00:00
|
|
|
|
|
|
|
/* Different device ID's? Don't know */
|
|
|
|
if (a->deviceid != b->deviceid)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Do we have dive IDs? */
|
|
|
|
if (!a->diveid || !b->diveid)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If they have different dive ID's on the same
|
|
|
|
* dive computer, that's a definite "same or not"
|
|
|
|
*/
|
|
|
|
return a->diveid == b->diveid ? 1 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Match every dive computer against each other to see if
|
|
|
|
* we have a matching dive.
|
|
|
|
*
|
|
|
|
* Return values:
|
|
|
|
* -1 for "is definitely *NOT* the same dive"
|
|
|
|
* 0 for "don't know"
|
|
|
|
* 1 for "is definitely the same dive"
|
|
|
|
*/
|
|
|
|
static int match_dc_dive(struct divecomputer *a, struct divecomputer *b)
|
|
|
|
{
|
|
|
|
do {
|
|
|
|
struct divecomputer *tmp = b;
|
|
|
|
do {
|
|
|
|
int match = match_one_dc(a, tmp);
|
|
|
|
if (match)
|
|
|
|
return match;
|
|
|
|
tmp = tmp->next;
|
|
|
|
} while (tmp);
|
|
|
|
a = a->next;
|
|
|
|
} while (a);
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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,
|
|
|
|
* but instead at download-time syncronizes its internal
|
|
|
|
* time with real-time on the downloading computer)
|
|
|
|
*
|
|
|
|
* - using multiple dive computers with different real time on
|
|
|
|
* the same dive
|
|
|
|
*
|
|
|
|
* We do not merge dives that look radically different, and if
|
|
|
|
* the dates are *too* far off the user will have to join two
|
|
|
|
* dives together manually. But this tries to handle the sane
|
|
|
|
* cases.
|
|
|
|
*/
|
|
|
|
static int likely_same_dive(struct dive *a, struct dive *b)
|
|
|
|
{
|
2013-01-23 19:53:42 +00:00
|
|
|
int match, fuzz = 20 * 60;
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
Allow overlapping (and disjoint) dive trips
We used to have the rule that a dive trip has to have all dives in it in
sequential order, even though our XML file really is much more flexible,
and allows arbitrary nesting of dives within a dive trip.
Put another way, the old model had fairly inflexible rules:
- the dive array is sorted by time
- a dive trip is always a contiguous slice of this sorted array
which makes perfect sense when you think of the dive and trip list as a
physical activity by one person, but leads to various very subtle issues
in the general case when there are no guarantees that the user then uses
subsurface that way.
In particular, if you load the XML files of two divers that have
overlapping dive trips, the end result is incredibly messy, and does not
conform to the above model at all.
There's two ways to enforce such conformance:
- disallow that kind of behavior entirely.
This is actually hard. Our XML files aren't date-based, they are
based on XML nesting rules, and even a single XML file can have
nesting that violates the date ordering. With multiple XML files,
it's trivial to do in practice, and while we could just fail at
loading, the failure would have to be a hard failure that leaves the
user no way to use the data at all.
- try to "fix it up" by sorting, splitting, and combining dive trips
automatically.
Dirk had a patch to do this, but it really does destroy the actual
dive data: if you load both mine and Dirk's dive trips, you ended up
with a result that followed the above two technical rules, but that
didn't actually make any *sense*.
So this patch doesn't try to enforce the rules, and instead just changes
them to be more generic:
- the dive array is still sorted by dive time
- a dive trip is just an arbitrary collection of dives.
The relaxed rules means that mixing dives and dive trips for two people
is trivial, and we can easily handle any XML file. The dive trip is
defined by the XML nesting level, and is totally independent of any
date-based sorting.
It does require a few things:
- when we save our dive data, we have to do it hierarchically by dive
trip, not just by walking the dive array linearly.
- similarly, when we create the dive tree model, we can't just blindly
walk the array of dives one by one, we have to look up the correct
trip (parent)
- when we try to merge two dives that are adjacent (by date sorting),
we can't do it if they are in different trips.
but apart from that, nothing else really changes.
NOTE! Despite the new relaxed model, creating totally disjoing dive
trips is not all that easy (nor is there any *reason* for it to be
easty). Our GUI interfaces still are "add dive to trip above" etc, and
the automatic adding of dives to dive trips is obviously still based on
date.
So this does not really change the expected normal usage, the relaxed
data structure rules just mean that we don't need to worry about the odd
cases as much, because we can just let them be.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
|
|
|
/* Don't try to merge dives in different trips */
|
|
|
|
if (a->divetrip && b->divetrip && a->divetrip != b->divetrip)
|
|
|
|
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()'
|
|
|
|
*/
|
2013-01-23 18:25:31 +00:00
|
|
|
if (!similar(a->dc.maxdepth.mm, b->dc.maxdepth.mm, 1000) ||
|
|
|
|
!similar(a->dc.meandepth.mm, b->dc.meandepth.mm, 1000) ||
|
2013-02-08 06:48:07 +00:00
|
|
|
!similar(get_duration_in_sec(a), get_duration_in_sec(b), 5*60))
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
return 0;
|
|
|
|
|
2012-12-16 18:55:58 +00:00
|
|
|
/* See if we can get an exact match on the dive computer */
|
|
|
|
match = match_dc_dive(&a->dc, &b->dc);
|
|
|
|
if (match)
|
|
|
|
return match > 0;
|
|
|
|
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
/*
|
2012-12-16 18:55:58 +00:00
|
|
|
* Allow a time difference due to dive computer time
|
|
|
|
* setting etc. Check if they overlap.
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
*/
|
2013-02-08 06:48:07 +00:00
|
|
|
fuzz = MAX(get_duration_in_sec(a), get_duration_in_sec(b)) / 2;
|
2012-12-16 18:55:58 +00:00
|
|
|
if (fuzz < 60)
|
|
|
|
fuzz = 60;
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
|
|
|
|
return ((a->when <= b->when + fuzz) && (a->when >= b->when - fuzz));
|
|
|
|
}
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
/*
|
|
|
|
* This could do a lot more merging. Right now it really only
|
|
|
|
* merges almost exact duplicates - something that happens easily
|
|
|
|
* with overlapping dive downloads.
|
|
|
|
*/
|
Add special download modes to force updates from the divecomputer
This will hopefully not be something we need often, but if we improve
support for a divecomputer (either in libdivecomputer or in our native
Uemis code or even in the way we handle (and potentially discard) events),
then it is extremely useful to be able to say "re-download things
from the divecomputer and for things that were not edited in Subsurface,
don't try to merge the data (which gives BAD results if for example you
fixed a bug in the depth calculation in libdivecomputer) but instead
simply take the samples, the events and some of the other unedited data
straight from the download".
This commit implements just that - a "force download" checkbox in the
download dialog that makes us reimport all dives from the dive computer,
even the ones we already have, and an "always prefer downloaded dive"
checkbox that then tells Subsurface not to merge but simply to take the
data from the downloaded dive - without overwriting the things we have
already edited in Subsurface (like location, buddy, equipment, etc).
This, as a precaution, refuses to merge dives that don't have identical
start times. So if you have edited the date / time of a dive or if you
have previously merged your dive with a different dive computer (and
therefore modified samples and events) you are out of luck.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-11 13:29:26 +00:00
|
|
|
struct dive *try_to_merge(struct dive *a, struct dive *b, gboolean prefer_downloaded)
|
2011-09-03 20:19:26 +00:00
|
|
|
{
|
Improve automatic dive merging logic
This tunes the heuristics for when to merge two dives together into a
single dive. We used to just look at the date, and say "if they're
within one minute of each other, try to merge". This looks at the
actual dive data, and tries to see just how much sense it makes to
merge the dive.
It also checks if the dives to be merged use different dive computers,
and if so relaxes the one minute to five, since most people aren't
quite as OCD as I am, and don't tend to set their dive computers quite
that exactly to the same time and date.
I'm sure people can come up with other heuristics, but this should
make that easier too.
NOTE! If you have things like wrong timezones etc, and the
divecomputer dates are thus off by hours rather than by a couple of
minutes, this will still not merge them. For that kind of situation,
we'd need some kind of manual merge option. Note that that is *not*
the same as the current "merge two adjacent dives" together, which
joins two separate dives into one *longer* dive with a surface
interval in between.
That kind of manual merge UI makes sense, but is independent of this
partical change.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-04 20:26:28 +00:00
|
|
|
if (likely_same_dive(a, b))
|
|
|
|
return merge_dives(a, b, 0, prefer_downloaded);
|
|
|
|
return NULL;
|
2012-11-25 04:29:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void free_events(struct event *ev)
|
|
|
|
{
|
|
|
|
while (ev) {
|
|
|
|
struct event *next = ev->next;
|
|
|
|
free(ev);
|
|
|
|
ev = next;
|
Add special download modes to force updates from the divecomputer
This will hopefully not be something we need often, but if we improve
support for a divecomputer (either in libdivecomputer or in our native
Uemis code or even in the way we handle (and potentially discard) events),
then it is extremely useful to be able to say "re-download things
from the divecomputer and for things that were not edited in Subsurface,
don't try to merge the data (which gives BAD results if for example you
fixed a bug in the depth calculation in libdivecomputer) but instead
simply take the samples, the events and some of the other unedited data
straight from the download".
This commit implements just that - a "force download" checkbox in the
download dialog that makes us reimport all dives from the dive computer,
even the ones we already have, and an "always prefer downloaded dive"
checkbox that then tells Subsurface not to merge but simply to take the
data from the downloaded dive - without overwriting the things we have
already edited in Subsurface (like location, buddy, equipment, etc).
This, as a precaution, refuses to merge dives that don't have identical
start times. So if you have edited the date / time of a dive or if you
have previously merged your dive with a different dive computer (and
therefore modified samples and events) you are out of luck.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-11 13:29:26 +00:00
|
|
|
}
|
2012-11-25 04:29:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void free_dc(struct divecomputer *dc)
|
|
|
|
{
|
|
|
|
free(dc->sample);
|
2012-12-24 01:53:27 +00:00
|
|
|
if (dc->model)
|
|
|
|
free((void *)dc->model);
|
2012-11-25 04:29:14 +00:00
|
|
|
free_events(dc->events);
|
|
|
|
free(dc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int same_event(struct event *a, struct event *b)
|
|
|
|
{
|
|
|
|
if (a->time.seconds != b->time.seconds)
|
|
|
|
return 0;
|
|
|
|
if (a->type != b->type)
|
|
|
|
return 0;
|
|
|
|
if (a->flags != b->flags)
|
|
|
|
return 0;
|
|
|
|
if (a->value != b->value)
|
|
|
|
return 0;
|
|
|
|
return !strcmp(a->name, b->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int same_sample(struct sample *a, struct sample *b)
|
|
|
|
{
|
|
|
|
if (a->time.seconds != b->time.seconds)
|
|
|
|
return 0;
|
|
|
|
if (a->depth.mm != b->depth.mm)
|
|
|
|
return 0;
|
|
|
|
if (a->temperature.mkelvin != b->temperature.mkelvin)
|
|
|
|
return 0;
|
|
|
|
if (a->cylinderpressure.mbar != b->cylinderpressure.mbar)
|
|
|
|
return 0;
|
First step in cleaning up cylinder pressure sensor logic
This clarifies/changes the meaning of our "cylinderindex" entry in our
samples. It has been rather confused, because different dive computers
have done things differently, and the naming really hasn't helped.
There are two totally different - and independent - cylinder "indexes":
- the pressure sensor index, which indicates which cylinder the sensor
data is from.
- the "active cylinder" index, which indicates which cylinder we actually
breathe from.
These two values really are totally independent, and have nothing
what-so-ever to do with each other. The sensor index may well be fixed:
many dive computers only support a single pressure sensor (whether
wireless or wired), and the sensor index is thus always zero.
Other dive computers may support multiple pressure sensors, and the gas
switch event may - or may not - indicate that the sensor changed too. A
dive computer might give the sensor data for *all* cylinders it can read,
regardless of which one is the one we're actively breathing. In fact, some
dive computers might give sensor data for not just *your* cylinder, but
your buddies.
This patch renames "cylinderindex" in the samples as "sensor", making it
quite clear that it's about which sensor index the pressure data in the
sample is about.
The way we figure out which is the currently active gas is with an
explicit has change event. If a computer (like the Uemis Zurich) joins the
two concepts together, then a sensor change should also create a gas
switch event. This patch also changes the Uemis importer to do that.
Finally, it should be noted that the plot info works totally separately
from the sample data, and is about what we actually *display*, not about
the sample pressures etc. In the plot info, the "cylinderindex" does in
fact mean the currently active cylinder, and while it is initially set to
match the sensor information from the samples, we then walk the gas change
events and fix it up - and if the active cylinder differs from the sensor
cylinder, we clear the sensor data.
[Dirk Hohndel: this conflicted with some of my recent changes - I think
I merged things correctly...]
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
|
|
|
return a->sensor == b->sensor;
|
2012-11-25 04:29:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int same_dc(struct divecomputer *a, struct divecomputer *b)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct event *eva, *evb;
|
|
|
|
|
2012-12-16 20:33:37 +00:00
|
|
|
i = match_one_dc(a, b);
|
|
|
|
if (i)
|
|
|
|
return i > 0;
|
|
|
|
|
2012-11-25 04:29:14 +00:00
|
|
|
if (a->when && b->when && a->when != b->when)
|
|
|
|
return 0;
|
|
|
|
if (a->samples != b->samples)
|
|
|
|
return 0;
|
|
|
|
for (i = 0; i < a->samples; i++)
|
|
|
|
if (!same_sample(a->sample+i, b->sample+i))
|
|
|
|
return 0;
|
|
|
|
eva = a->events;
|
|
|
|
evb = b->events;
|
|
|
|
while (eva && evb) {
|
|
|
|
if (!same_event(eva, evb))
|
|
|
|
return 0;
|
|
|
|
eva = eva->next;
|
|
|
|
evb = evb->next;
|
|
|
|
}
|
|
|
|
return eva == evb;
|
|
|
|
}
|
|
|
|
|
2013-01-23 00:57:07 +00:00
|
|
|
static int might_be_same_device(struct divecomputer *a, struct divecomputer *b)
|
|
|
|
{
|
|
|
|
/* No dive computer model? That matches anything */
|
|
|
|
if (!a->model || !b->model)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
/* Otherwise at least the model names have to match */
|
|
|
|
if (strcasecmp(a->model, b->model))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* No device ID? Match */
|
|
|
|
if (!a->deviceid || !b->deviceid)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return a->deviceid == b->deviceid;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void remove_redundant_dc(struct divecomputer *dc, int prefer_downloaded)
|
2012-11-25 04:29:14 +00:00
|
|
|
{
|
|
|
|
do {
|
|
|
|
struct divecomputer **p = &dc->next;
|
|
|
|
|
|
|
|
/* Check this dc against all the following ones.. */
|
|
|
|
while (*p) {
|
|
|
|
struct divecomputer *check = *p;
|
2013-01-23 00:57:07 +00:00
|
|
|
if (same_dc(dc, check) || (prefer_downloaded && might_be_same_device(dc, check))) {
|
2012-11-25 04:29:14 +00:00
|
|
|
*p = check->next;
|
|
|
|
check->next = NULL;
|
|
|
|
free_dc(check);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
p = &check->next;
|
|
|
|
}
|
|
|
|
|
2013-01-23 00:57:07 +00:00
|
|
|
/* .. and then continue down the chain, but we */
|
|
|
|
prefer_downloaded = 0;
|
2012-11-25 04:29:14 +00:00
|
|
|
dc = dc->next;
|
|
|
|
} while (dc);
|
2012-11-11 06:20:05 +00:00
|
|
|
}
|
|
|
|
|
2012-11-25 20:39:08 +00:00
|
|
|
static void clear_dc(struct divecomputer *dc)
|
|
|
|
{
|
|
|
|
memset(dc, 0, sizeof(*dc));
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct divecomputer *find_matching_computer(struct divecomputer *match, struct divecomputer *list)
|
|
|
|
{
|
|
|
|
struct divecomputer *p;
|
|
|
|
|
|
|
|
while ((p = list) != NULL) {
|
|
|
|
list = list->next;
|
|
|
|
|
2013-01-23 00:57:07 +00:00
|
|
|
if (might_be_same_device(match, p))
|
2012-11-25 20:39:08 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
2013-02-04 05:21:47 +00:00
|
|
|
|
|
|
|
static void copy_dive_computer(struct divecomputer *res, struct divecomputer *a)
|
|
|
|
{
|
|
|
|
*res = *a;
|
|
|
|
res->model = a->model ? strdup(a->model) : NULL;
|
|
|
|
res->samples = res->alloc_samples = 0;
|
|
|
|
res->sample = NULL;
|
|
|
|
res->events = NULL;
|
|
|
|
res->next = NULL;
|
|
|
|
}
|
|
|
|
|
2012-11-25 20:39:08 +00:00
|
|
|
/*
|
|
|
|
* Join dive computers with a specific time offset between
|
|
|
|
* them.
|
|
|
|
*
|
|
|
|
* Use the dive computer ID's (or names, if ID's are missing)
|
|
|
|
* to match them up. If we find a matching dive computer, we
|
|
|
|
* merge them. If not, we just take the data from 'a'.
|
|
|
|
*/
|
|
|
|
static void interleave_dive_computers(struct divecomputer *res,
|
|
|
|
struct divecomputer *a, struct divecomputer *b, int offset)
|
|
|
|
{
|
|
|
|
do {
|
|
|
|
struct divecomputer *match;
|
|
|
|
|
2013-02-04 05:21:47 +00:00
|
|
|
copy_dive_computer(res, a);
|
2012-11-25 20:39:08 +00:00
|
|
|
|
|
|
|
match = find_matching_computer(a, b);
|
|
|
|
if (match) {
|
|
|
|
merge_events(res, a, match, offset);
|
|
|
|
merge_samples(res, a, match, offset);
|
|
|
|
} else {
|
|
|
|
res->sample = a->sample;
|
|
|
|
res->samples = a->samples;
|
|
|
|
res->events = a->events;
|
|
|
|
a->sample = NULL;
|
|
|
|
a->samples = 0;
|
|
|
|
a->events = NULL;
|
|
|
|
}
|
|
|
|
a = a->next;
|
|
|
|
if (!a)
|
|
|
|
break;
|
|
|
|
res->next = calloc(1, sizeof(struct divecomputer));
|
|
|
|
res = res->next;
|
|
|
|
} while (res);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Join dive computer information.
|
|
|
|
*
|
|
|
|
* If we have old-style dive computer information (no model
|
|
|
|
* name etc), we will prefer a new-style one and just throw
|
|
|
|
* away the old. We're assuming it's a re-download.
|
|
|
|
*
|
2013-01-23 00:57:07 +00:00
|
|
|
* Otherwise, we'll just try to keep all the information,
|
|
|
|
* unless the user has specified that they prefer the
|
|
|
|
* downloaded computer, in which case we'll aggressively
|
|
|
|
* try to throw out old information that *might* be from
|
|
|
|
* that one.
|
2012-11-25 20:39:08 +00:00
|
|
|
*/
|
2013-01-23 00:57:07 +00:00
|
|
|
static void join_dive_computers(struct divecomputer *res, struct divecomputer *a, struct divecomputer *b, int prefer_downloaded)
|
2012-11-25 20:39:08 +00:00
|
|
|
{
|
2012-12-16 20:33:37 +00:00
|
|
|
struct divecomputer *tmp;
|
|
|
|
|
2012-11-25 20:39:08 +00:00
|
|
|
if (a->model && !b->model) {
|
|
|
|
*res = *a;
|
|
|
|
clear_dc(a);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (b->model && !a->model) {
|
|
|
|
*res = *b;
|
|
|
|
clear_dc(b);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
*res = *a;
|
|
|
|
clear_dc(a);
|
2012-12-16 20:33:37 +00:00
|
|
|
tmp = res;
|
|
|
|
while (tmp->next)
|
|
|
|
tmp = tmp->next;
|
2012-11-25 20:39:08 +00:00
|
|
|
|
2012-12-16 20:33:37 +00:00
|
|
|
tmp->next = calloc(1, sizeof(*tmp));
|
|
|
|
*tmp->next = *b;
|
2012-11-25 20:39:08 +00:00
|
|
|
clear_dc(b);
|
|
|
|
|
2013-01-23 00:57:07 +00:00
|
|
|
remove_redundant_dc(res, prefer_downloaded);
|
2012-11-25 20:39:08 +00:00
|
|
|
}
|
|
|
|
|
Add special download modes to force updates from the divecomputer
This will hopefully not be something we need often, but if we improve
support for a divecomputer (either in libdivecomputer or in our native
Uemis code or even in the way we handle (and potentially discard) events),
then it is extremely useful to be able to say "re-download things
from the divecomputer and for things that were not edited in Subsurface,
don't try to merge the data (which gives BAD results if for example you
fixed a bug in the depth calculation in libdivecomputer) but instead
simply take the samples, the events and some of the other unedited data
straight from the download".
This commit implements just that - a "force download" checkbox in the
download dialog that makes us reimport all dives from the dive computer,
even the ones we already have, and an "always prefer downloaded dive"
checkbox that then tells Subsurface not to merge but simply to take the
data from the downloaded dive - without overwriting the things we have
already edited in Subsurface (like location, buddy, equipment, etc).
This, as a precaution, refuses to merge dives that don't have identical
start times. So if you have edited the date / time of a dive or if you
have previously merged your dive with a different dive computer (and
therefore modified samples and events) you are out of luck.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-11 13:29:26 +00:00
|
|
|
struct dive *merge_dives(struct dive *a, struct dive *b, int offset, gboolean prefer_downloaded)
|
2012-11-11 06:20:05 +00:00
|
|
|
{
|
|
|
|
struct dive *res = alloc_dive();
|
Add special download modes to force updates from the divecomputer
This will hopefully not be something we need often, but if we improve
support for a divecomputer (either in libdivecomputer or in our native
Uemis code or even in the way we handle (and potentially discard) events),
then it is extremely useful to be able to say "re-download things
from the divecomputer and for things that were not edited in Subsurface,
don't try to merge the data (which gives BAD results if for example you
fixed a bug in the depth calculation in libdivecomputer) but instead
simply take the samples, the events and some of the other unedited data
straight from the download".
This commit implements just that - a "force download" checkbox in the
download dialog that makes us reimport all dives from the dive computer,
even the ones we already have, and an "always prefer downloaded dive"
checkbox that then tells Subsurface not to merge but simply to take the
data from the downloaded dive - without overwriting the things we have
already edited in Subsurface (like location, buddy, equipment, etc).
This, as a precaution, refuses to merge dives that don't have identical
start times. So if you have edited the date / time of a dive or if you
have previously merged your dive with a different dive computer (and
therefore modified samples and events) you are out of luck.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-11 13:29:26 +00:00
|
|
|
struct dive *dl = NULL;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2012-12-07 17:22:35 +00:00
|
|
|
/* Aim for newly downloaded dives to be 'b' (keep old dive data first) */
|
|
|
|
if (a->downloaded && !b->downloaded) {
|
|
|
|
struct dive *tmp = a;
|
|
|
|
a = b;
|
|
|
|
b = tmp;
|
2012-11-21 23:34:04 +00:00
|
|
|
}
|
2012-12-07 17:22:35 +00:00
|
|
|
if (prefer_downloaded && b->downloaded)
|
|
|
|
dl = b;
|
|
|
|
|
2012-11-25 04:29:14 +00:00
|
|
|
res->when = dl ? dl->when : a->when;
|
2012-11-11 10:00:27 +00:00
|
|
|
res->selected = a->selected || b->selected;
|
2012-11-11 06:20:05 +00:00
|
|
|
merge_trip(res, a, b);
|
2012-12-05 17:59:52 +00:00
|
|
|
MERGE_NONZERO(res, a, b, latitude.udeg);
|
|
|
|
MERGE_NONZERO(res, a, b, longitude.udeg);
|
2011-09-23 01:01:01 +00:00
|
|
|
MERGE_TXT(res, a, b, location);
|
|
|
|
MERGE_TXT(res, a, b, notes);
|
|
|
|
MERGE_TXT(res, a, b, buddy);
|
|
|
|
MERGE_TXT(res, a, b, divemaster);
|
2011-12-07 19:58:16 +00:00
|
|
|
MERGE_MAX(res, a, b, rating);
|
2012-08-14 23:07:25 +00:00
|
|
|
MERGE_TXT(res, a, b, suit);
|
2011-09-12 19:56:34 +00:00
|
|
|
MERGE_MAX(res, a, b, number);
|
2013-01-23 18:25:31 +00:00
|
|
|
res->dc.meandepth.mm = 0;
|
2012-12-11 20:30:34 +00:00
|
|
|
MERGE_NONZERO(res, a, b, cns);
|
2012-11-10 18:18:10 +00:00
|
|
|
MERGE_NONZERO(res, a, b, visibility);
|
2012-10-29 18:27:14 +00:00
|
|
|
merge_equipment(res, a, b);
|
Add special download modes to force updates from the divecomputer
This will hopefully not be something we need often, but if we improve
support for a divecomputer (either in libdivecomputer or in our native
Uemis code or even in the way we handle (and potentially discard) events),
then it is extremely useful to be able to say "re-download things
from the divecomputer and for things that were not edited in Subsurface,
don't try to merge the data (which gives BAD results if for example you
fixed a bug in the depth calculation in libdivecomputer) but instead
simply take the samples, the events and some of the other unedited data
straight from the download".
This commit implements just that - a "force download" checkbox in the
download dialog that makes us reimport all dives from the dive computer,
even the ones we already have, and an "always prefer downloaded dive"
checkbox that then tells Subsurface not to merge but simply to take the
data from the downloaded dive - without overwriting the things we have
already edited in Subsurface (like location, buddy, equipment, etc).
This, as a precaution, refuses to merge dives that don't have identical
start times. So if you have edited the date / time of a dive or if you
have previously merged your dive with a different dive computer (and
therefore modified samples and events) you are out of luck.
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-11 13:29:26 +00:00
|
|
|
if (dl) {
|
2013-01-23 00:57:07 +00:00
|
|
|
/* If we prefer downloaded, do those first, and get rid of "might be same" computers */
|
|
|
|
join_dive_computers(&res->dc, &dl->dc, &a->dc, 1);
|
2012-11-25 20:39:08 +00:00
|
|
|
} else if (offset)
|
|
|
|
interleave_dive_computers(&res->dc, &a->dc, &b->dc, offset);
|
|
|
|
else
|
2013-01-23 00:57:07 +00:00
|
|
|
join_dive_computers(&res->dc, &a->dc, &b->dc, 0);
|
2012-11-25 04:29:14 +00:00
|
|
|
|
2012-11-24 02:51:27 +00:00
|
|
|
fixup_dive(res);
|
|
|
|
return res;
|
2011-09-03 20:19:26 +00:00
|
|
|
}
|
2013-01-31 03:09:16 +00:00
|
|
|
|
|
|
|
struct dive *find_dive_including(timestamp_t when)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct dive *dive;
|
|
|
|
|
|
|
|
/* binary search, anyone? Too lazy for now;
|
|
|
|
* also we always use the duration from the first divecomputer
|
|
|
|
* could this ever be a problem? */
|
|
|
|
for_each_dive(i, dive) {
|
2013-02-08 06:48:07 +00:00
|
|
|
if (dive->when <= when && when <= dive->when + get_duration_in_sec(dive))
|
2013-01-31 03:09:16 +00:00
|
|
|
return dive;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean dive_within_time_range(struct dive *dive, timestamp_t when, timestamp_t offset)
|
|
|
|
{
|
2013-02-08 06:48:07 +00:00
|
|
|
return when - offset <= dive->when && dive->when + get_duration_in_sec(dive) <= when + offset;
|
2013-01-31 03:09:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* find the n-th dive that is part of a group of dives within the offset around 'when'.
|
|
|
|
* How is that for a vague definition of what this function should do... */
|
|
|
|
struct dive *find_dive_n_near(timestamp_t when, int n, timestamp_t offset)
|
|
|
|
{
|
|
|
|
int i, j = 0;
|
|
|
|
struct dive *dive;
|
|
|
|
|
|
|
|
for_each_dive(i, dive) {
|
|
|
|
if (dive_within_time_range(dive, when, offset))
|
|
|
|
if (++j == n)
|
|
|
|
return dive;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|