Real support for multiple concurrent sensors..

Linus Torvalds torvalds at linux-foundation.org
Wed Jul 26 21:57:27 PDT 2017


Ok, I've actually spent a fair amount of time on this patch, and it 
actually seems to work.

It really handles multiple cylinder pressures, both overlapping and 
consecutive, and it seems to work on the nasty cases I've thrown at it. 
Want to just track five different cylinders all at once, without any 
pesky gas switch events? Sure, you can do that. It will show five 
different gas pressures for your five cylinders, and they will go down as 
you breathe down the cylinders.

I obviously don't have any real data for that case, but I do have a test 
file with five actual cylinders that all have samples over the whole 
course of the dive. The end result looks messy as hell, but what did you 
expect?

HOWEVER.

The only way to do this sanely was

 - actually make the "struct plot_info" have all the cylinder pressures 
   (so no "sensor index and pressure" - every cylinder has a pressure for
   every plot info entry)

   This obviously makes the plot_info much bigger. We used to have 
   MAX_CYLINDERS be a fairly generous 8, which seems sane. The planning 
   code made that 8 be 20. That seems questionable. But whatever.

   The good news is that the plot-info should hopefully get freed, and 
   only be allocated one dive at a time, so the fact that it is big and 
   nasty shouldn't be a scaling issue, though.

 - the "populate_pressure_information()" function had to be rewritten 
   quite a bit. The good news is that it's actually simpler now, although 
   I would not go so far as to really call it simple. It's still 
   complicated and suble, but now it explicitly just does one cylinder at 
   a time.

   It *used* to have this insanely complicated "keep track of the pressure 
   ranges for every cylinder at once". I just couldn't stand that model 
   and keep my sanity, so it now just tracks one cylinder at a time, and 
   doesn't have an array of live data, instead the caller will just call 
   it for each cylinder.

 - get rid of some of our hackier stuff, like the code that populates the 
   plot_info data code with the currently selected cylinder number, and 
   clears out any other pressures. That obviously does *not* work when you 
   may not have a single primary cylinder any more.

Now, the above sounds like all good things. Yeah, it mostly is. BUT.

There's a few big downsides from the above:

 - there's no sane way to do this as a series of small changes. 

   The change to make the plot_info take an array of cylinder pressures 
   rather than the sensor+pressure model really isn't amenable to "fix up 
   one use at a time". When you switch over to the new data structure 
   model, you have to switch over to the new way of populating the 
   pressure ranges. The two just go hand in hand.

 - Some of our code *depended* on the "sensor+pressure" model. I fixed all 
   the ones I could sanely fix. There was one particular case that I just 
   couldn't sanely fix, and I didn't care enough about it to do something
   insane.

   So the only _known_ breakage is the "TankItem" profile widget. That's 
   the bar at the bottom of the profile that shows which cylinder is in
   use right now. You'd think that would be trivial to fix up, and yes it 
   would be - I could just use the regular model of

     firstcyl = explicit_first_cylinder(dive, dc)
     .. then iterate over the gas change events to see the others ..

   but the problem with the "TankItem" widget is that it does its own 
   model, and it has thrown away the dive and the dive computer 
   information. It just doesn't even know. It only knows what cylinders 
   there are, and the plot_info. And it just used to look at the sensor 
   number in the plot_info, and be done with that. That number no longer 
   exists.

 - I have tested it, and I think the code is better, but hey, it's a 
   fairly large patch to some of the more complex code in our code base. 
   That "interpolate missing pressure fields" code really isn't pretty. It 
   may be prettier, but..

Anyway, without further ado, here's the patch. No sign-off yet, because I 
do think people should look and comment. But I think the patch is fine, 
and I'll fix anythign that anybody can find, *except* for that TankItem 
thing that I will refuse to touch. That class is ugly. It needs to have 
access to the actual dive.

Note how it actually does remove more lines than it adds, and that's 
despite added comments etc. The code really is simpler, but there may be 
cases in there that need more work.

              Linus

---
 core/dive.c                        |  10 +-
 core/gaspressures.c                | 205 ++++++++++++++++++++++---------------
 core/profile.c                     | 191 +++++++++++++---------------------
 core/profile.h                     |   9 +-
 profile-widget/diveprofileitem.cpp | 131 +++++++++++-------------
 profile-widget/profilewidget2.cpp  |   2 -
 profile-widget/tankitem.cpp        |  10 +-
 qt-models/diveplotdatamodel.cpp    |   2 +-
 8 files changed, 274 insertions(+), 286 deletions(-)

diff --git a/core/dive.c b/core/dive.c
index 42ef687e..d07a9d2a 100644
--- a/core/dive.c
+++ b/core/dive.c
@@ -1498,24 +1498,26 @@ static void fixup_dive_pressures(struct dive *dive, struct divecomputer *dc)
 
 	/* Walk the samples from the beginning to find starting pressures.. */
 	for (i = 0; i < dc->samples; i++) {
+		int idx;
 		struct sample *sample = dc->sample + i;
 
 		if (sample->depth.mm < SURFACE_THRESHOLD)
 			continue;
 
-		fixup_start_pressure(dive, sample->sensor[0], sample->pressure[0]);
-		fixup_start_pressure(dive, sample->sensor[1], sample->pressure[1]);
+		for (idx = 0; idx < MAX_SENSORS; idx++)
+			fixup_start_pressure(dive, sample->sensor[idx], sample->pressure[idx]);
 	}
 
 	/* ..and from the end for ending pressures */
 	for (i = dc->samples; --i >= 0; ) {
+		int idx;
 		struct sample *sample = dc->sample + i;
 
 		if (sample->depth.mm < SURFACE_THRESHOLD)
 			continue;
 
-		fixup_end_pressure(dive, sample->sensor[0], sample->pressure[0]);
-		fixup_end_pressure(dive, sample->sensor[1], sample->pressure[1]);
+		for (idx = 0; idx < MAX_SENSORS; idx++)
+			fixup_end_pressure(dive, sample->sensor[idx], sample->pressure[idx]);
 	}
 
 	simplify_dc_pressures(dc);
diff --git a/core/gaspressures.c b/core/gaspressures.c
index 86ea8de7..13196b61 100644
--- a/core/gaspressures.c
+++ b/core/gaspressures.c
@@ -64,18 +64,20 @@ static void list_free(pr_track_t *list)
 }
 
 #ifdef DEBUG_PR_TRACK
-static void dump_pr_track(pr_track_t **track_pr)
+static void dump_pr_track(int cyl, pr_track_t *track_pr)
 {
-	int cyl;
 	pr_track_t *list;
 
-	for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) {
-		list = track_pr[cyl];
-		while (list) {
-			printf("cyl%d: start %d end %d t_start %d t_end %d pt %d\n", cyl,
-			       list->start, list->end, list->t_start, list->t_end, list->pressure_time);
-			list = list->next;
-		}
+	printf("cyl%d:\n", cyl);
+	list = track_pr;
+	while (list) {
+		printf("   start %d end %d t_start %d:%02d t_end %d:%02d pt %d\n",
+		       mbar_to_PSI(list->start),
+		       mbar_to_PSI(list->end),
+		       FRACTION(list->t_start, 60),
+		       FRACTION(list->t_end, 60),
+		       list->pressure_time);
+		list = list->next;
 	}
 }
 #endif
@@ -197,32 +199,28 @@ static struct pr_interpolate_struct get_pr_interpolate_data(pr_track_t *segment,
 	return interpolate;
 }
 
-static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, pr_track_t **track_pr, int sensoridx)
+static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, pr_track_t *track_pr, int cyl)
 {
-	int cyl, i;
+	int i;
 	struct plot_data *entry;
 	pr_interpolate_t interpolate = { 0, 0, 0, 0 };
 	pr_track_t *last_segment = NULL;
-	int cur_pr[MAX_CYLINDERS]; // cur_pr[MAX_CYLINDERS] is the CCR diluent cylinder
+	int cur_pr;
+	enum interpolation_strategy strategy;
 
-	for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) {
-		enum interpolation_strategy strategy;
-		if (!track_pr[cyl]) {
-			/* no segment where this cylinder is used */
-			cur_pr[cyl] = -1;
-			continue;
-		}
-		if (dive->cylinder[cyl].cylinder_use == OC_GAS)
-			strategy = SAC;
-		else
-			strategy = TIME;
-		fill_missing_segment_pressures(track_pr[cyl], strategy); // Interpolate the missing tank pressure values ..
-		cur_pr[cyl] = track_pr[cyl]->start;	       // in the pr_track_t lists of structures
-	}						       // and keep the starting pressure for each cylinder.
+	/* no segment where this cylinder is used */
+	if (!track_pr)
+		return;
 
+	if (dive->cylinder[cyl].cylinder_use == OC_GAS)
+		strategy = SAC;
+	else
+		strategy = TIME;
+	fill_missing_segment_pressures(track_pr, strategy); // Interpolate the missing tank pressure values ..
+	cur_pr = track_pr->start;			       // in the pr_track_t lists of structures
+							       // and keep the starting pressure for each cylinder.
 #ifdef DEBUG_PR_TRACK
-	/* another great debugging tool */
-	dump_pr_track(track_pr);
+	dump_pr_track(cyl, track_pr);
 #endif
 
 	/* Transfer interpolated cylinder pressures from pr_track strucktures to plotdata
@@ -243,27 +241,32 @@ static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi,
 
 		entry = pi->entry + i;
 
-		cyl = entry->sensor[sensoridx];
-		if (cyl < 0)
-			continue;
-		save_pressure = &(entry->pressure[sensoridx][SENSOR_PR]);
-		save_interpolated = &(entry->pressure[sensoridx][INTERPOLATED_PR]);
+		save_pressure = &(entry->pressure[cyl][SENSOR_PR]);
+		save_interpolated = &(entry->pressure[cyl][INTERPOLATED_PR]);
 		pressure = *save_pressure ? *save_pressure : *save_interpolated;
 
 		if (pressure) {			// If there is a valid pressure value,
 			last_segment = NULL;	// get rid of interpolation data,
-			cur_pr[cyl] = pressure; // set current pressure
+			cur_pr = pressure; // set current pressure
 			continue;		// and skip to next point.
 		}
 		// If there is NO valid pressure value..
 		// Find the pressure segment corresponding to this entry..
-		segment = track_pr[cyl];
+		segment = track_pr;
 		while (segment && segment->t_end < entry->sec) // Find the track_pr with end time..
 			segment = segment->next;	       // ..that matches the plot_info time (entry->sec)
 
-		if (!segment || !segment->pressure_time) { // No (or empty) segment?
-			*save_pressure = cur_pr[cyl];      // Just use our current pressure
-			continue;			   // and skip to next point.
+		// After last segment? All done.
+		if (!segment)
+			break;
+
+		// Before first segment, or between segments.. Go on, no interpolation.
+		if (segment->t_start > entry->sec)
+			continue;
+
+		if (!segment->pressure_time) {		// Empty segment?
+			*save_pressure = cur_pr;	// Just use our current pressure
+			continue;			// and skip to next point.
 		}
 
 		// If there is a valid segment but no tank pressure ..
@@ -283,13 +286,13 @@ static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi,
 				magic = (interpolate.end - interpolate.start) / (double)interpolate.pressure_time;
 
 				/* Use that overall pressure change to update the current pressure */
-				cur_pr[cyl] = lrint(interpolate.start + magic * interpolate.acc_pressure_time);
+				cur_pr = lrint(interpolate.start + magic * interpolate.acc_pressure_time);
 			}
 		} else {
 			magic = (interpolate.end - interpolate.start) /  (segment->t_end - segment->t_start);
-			cur_pr[cyl] = lrint(segment->start + magic * (entry->sec - segment->t_start));
+			cur_pr = lrint(segment->start + magic * (entry->sec - segment->t_start));
 		}
-		*save_interpolated = cur_pr[cyl]; // and store the interpolated data in plot_info
+		*save_interpolated = cur_pr; // and store the interpolated data in plot_info
 	}
 }
 
@@ -328,6 +331,8 @@ static void debug_print_pressures(struct plot_info *pi)
 }
 #endif
 
+extern bool has_gaschange_event(struct dive *dive, struct divecomputer *dc, int idx);
+
 /* This function goes through the list of tank pressures, either SENSOR_PRESSURE(entry) or O2CYLINDER_PRESSURE(entry),
  * of structure plot_info for the dive profile where each item in the list corresponds to one point (node) of the
  * profile. It finds values for which there are no tank pressures (pressure==0). For each missing item (node) of
@@ -338,78 +343,116 @@ static void debug_print_pressures(struct plot_info *pi)
  * in the pr_track_alloc structures. If diluent_flag = 1, then DILUENT_PRESSURE(entry) is used instead of SENSOR_PRESSURE.
  * This function is called by create_plot_info_new() in profile.c
  */
-void populate_pressure_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, int sensoridx)
+void populate_pressure_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, int sensor)
 {
 	(void) dc;
-	int i, cylinderid, cylinderindex = -1;
-	pr_track_t *track_pr[MAX_CYLINDERS] = { NULL, };
+	int first, last, cyl;
+	cylinder_t *cylinder = dive->cylinder + sensor;
+	pr_track_t *track = NULL;
 	pr_track_t *current = NULL;
-	bool missing_pr = false;
-	bool found_any_pr_data = false;
+	struct plot_data *entry;
+	struct event *ev;
+	int missing_pr = 0, dense = 1;
 
 	/* if we have no pressure data whatsoever, this is pointless, so let's just return */
-	for (i = 0; i < MAX_CYLINDERS; i++) {
-		if (dive->cylinder[i].start.mbar || dive->cylinder[i].sample_start.mbar ||
-		    dive->cylinder[i].end.mbar || dive->cylinder[i].sample_end.mbar) {
-			found_any_pr_data = true;
-			break;
-		}
-	}
-	if (!found_any_pr_data)
+	if (!cylinder->start.mbar && !cylinder->end.mbar &&
+	    !cylinder->sample_start.mbar && !cylinder->sample_end.mbar)
 		return;
 
-	for (i = 0; i < pi->nr; i++) {
+	/* Get a rough range of where we have any pressures at all */
+	first = last = -1;
+	for (int i = 0; i < pi->nr; i++) {
 		struct plot_data *entry = pi->entry + i;
-		unsigned pressure;
+		unsigned pressure = SENSOR_PRESSURE(entry, sensor);
+
+		if (!pressure)
+			continue;
+		if (first < 0)
+			first = i;
+		last = i;
+	}
 
-		pressure = SENSOR_PRESSURE(entry, sensoridx);
-		cylinderid = entry->sensor[sensoridx];
-		if (cylinderid < 0)
-			goto GIVE_UP;
+	/* No sensor data at all? */
+	if (first == last)
+		return;
+
+	/*
+	 * Split the range:
+	 *  - missing pressure data
+	 *  - gas change events to other cylinders
+	 *
+	 * Note that we only look at gas switches if this cylinder
+	 * itself has a gas change event.
+	 */
+	cyl = sensor;
+	ev = NULL;
+	if (has_gaschange_event(dive, dc, sensor))
+		ev = get_next_event(dc->events, "gaschange");
+
+	for (int i = first; i <= last; i++) {
+		struct plot_data *entry = pi->entry + i;
+		unsigned pressure = SENSOR_PRESSURE(entry, sensor);
+		int time = entry->sec;
+
+		while (ev && ev->time.seconds <= time) {
+			cyl = get_cylinder_index(dive, ev);
+			if (cyl < 0)
+				cyl = sensor;
+			ev = get_next_event(ev->next, "gaschange");
+		}
 
-		/* If track_pr structure already exists, then update it: */
-		/* discrete integration of pressure over time to get the SAC rate equivalent */
 		if (current) {
 			entry->pressure_time = calc_pressure_time(dive, entry - 1, entry);
 			current->pressure_time += entry->pressure_time;
 			current->t_end = entry->sec;
+			if (pressure)
+				current->end = pressure;
 		}
 
-		/* If 1st record or different cylinder: Create a new track_pr structure: */
-		/* track the segments per cylinder and their pressure/time integral */
-		if (cylinderid != cylinderindex) {
-			cylinderindex = entry->sensor[sensoridx];
-			current = pr_track_alloc(pressure, entry->sec);
-			track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current);
+		// If we have no pressure information, we will need to
+		// continue with or without a tracking entry. Mark any
+		// existing tracking entry as non-dense, and remember
+		// to fill in interpolated data.
+		if (!pressure) {
+			missing_pr = 1;
+			dense = 0;
 			continue;
 		}
 
-		if (!pressure) {
-			missing_pr = 1;
+		// We have a final pressure for 'current'
+		// If a gas switch has occurred, finish the
+		// current pressure track entry and continue
+		// until we get back to this cylinder.
+		if (cyl != sensor) {
+			current = NULL;
+			SENSOR_PRESSURE(entry, sensor) = 0;
 			continue;
 		}
-		if (current)
-			current->end = pressure;
 
-		/* Was it continuous? */
-		if (SENSOR_PRESSURE(entry - 1, sensoridx))
+		// If we already have a pressure tracking entry, and
+		// it has not had any missing samples, just continue
+		// using it - there's nothing to interpolate yet.
+		if (current && dense)
 			continue;
 
-		/* transmitter stopped transmitting cylinder pressure data */
+		// We need to start a new tracking entry, either
+		// because the previous was interrupted by a gas
+		// switch event, or because the previous one has
+		// missing entries that need to be interpolated.
+		// Or maybe we didn't have a previous one at all,
+		// and this is the first pressure entry.
 		current = pr_track_alloc(pressure, entry->sec);
-		if (cylinderindex >= 0)
-			track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current);
+		track = list_add(track, current);
+		dense = 1;
 	}
 
 	if (missing_pr) {
-		fill_missing_tank_pressures(dive, pi, track_pr, sensoridx);
+		fill_missing_tank_pressures(dive, pi, track, sensor);
 	}
 
 #ifdef PRINT_PRESSURES_DEBUG
 	debug_print_pressures(pi);
 #endif
 
-GIVE_UP:
-	for (i = 0; i < MAX_CYLINDERS; i++)
-		list_free(track_pr[i]);
+	list_free(track);
 }
diff --git a/core/profile.c b/core/profile.c
index 1b5c3662..6e216ec0 100644
--- a/core/profile.c
+++ b/core/profile.c
@@ -170,15 +170,13 @@ static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, str
 /* Get local sac-rate (in ml/min) between entry1 and entry2 */
 static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive)
 {
-	int index = entry1->sensor[0];
+	int index = 0;
 	cylinder_t *cyl;
 	int duration = entry2->sec - entry1->sec;
 	int depth, airuse;
 	pressure_t a, b;
 	double atm;
 
-	if (entry2->sensor[0] != index)
-		return 0;
 	if (duration <= 0)
 		return 0;
 	a.mbar = GET_PRESSURE(entry1, 0);
@@ -362,21 +360,6 @@ static int count_events(struct divecomputer *dc)
 	return result;
 }
 
-static int set_cylinder_index(struct plot_info *pi, int i, int cylinderindex, int end)
-{
-	while (i < pi->nr) {
-		struct plot_data *entry = pi->entry + i;
-		if (entry->sec > end)
-			break;
-		if (entry->sensor[0] != cylinderindex) {
-			entry->sensor[0] = cylinderindex;
-			entry->pressure[0][0] = 0;
-		}
-		i++;
-	}
-	return i;
-}
-
 static int set_setpoint(struct plot_info *pi, int i, int setpoint, int end)
 {
 	while (i < pi->nr) {
@@ -389,40 +372,6 @@ static int set_setpoint(struct plot_info *pi, int i, int setpoint, int end)
 	return i;
 }
 
-/* normally the first cylinder has index 0... if not, we need to fix this up here */
-static int set_first_cylinder_index(struct plot_info *pi, int i, int cylinderindex, int end)
-{
-	while (i < pi->nr) {
-		struct plot_data *entry = pi->entry + i;
-		if (entry->sec > end)
-			break;
-		entry->sensor[0] = cylinderindex;
-		i++;
-	}
-	return i;
-}
-
-static void check_gas_change_events(struct dive *dive, struct divecomputer *dc, struct plot_info *pi)
-{
-	int i = 0, cylinderindex = 0;
-	struct event *ev = get_next_event(dc->events, "gaschange");
-
-	// for dive computers that tell us their first gas as an event on the first sample
-	// we need to make sure things are setup correctly
-	cylinderindex = explicit_first_cylinder(dive, dc);
-	set_first_cylinder_index(pi, 0, cylinderindex, INT_MAX);
-
-	if (!ev)
-		return;
-
-	do {
-		i = set_cylinder_index(pi, i, cylinderindex, ev->time.seconds);
-		cylinderindex = get_cylinder_index(dive, ev);
-		ev = get_next_event(ev->next, "gaschange");
-	} while (ev);
-	set_cylinder_index(pi, i, cylinderindex, INT_MAX);
-}
-
 static void check_setpoint_events(struct dive *dive, struct divecomputer *dc, struct plot_info *pi)
 {
 	int i = 0;
@@ -639,11 +588,10 @@ struct plot_data *populate_plot_entries(struct dive *dive, struct divecomputer *
 		} else {
 			entry->pressures.o2 = sample->setpoint.mbar / 1000.0;
 		}
-		/* FIXME! sensor index -> cylinder index translation! */
-		entry->sensor[0] = sample->sensor[0];
-		entry->sensor[1] = sample->sensor[1];
-		SENSOR_PRESSURE(entry, 0) = sample->pressure[0].mbar;
-		SENSOR_PRESSURE(entry, 1) = sample->pressure[1].mbar;
+		if (sample->pressure[0].mbar)
+			SENSOR_PRESSURE(entry, sample->sensor[0]) = sample->pressure[0].mbar;
+		if (sample->pressure[1].mbar)
+			SENSOR_PRESSURE(entry, sample->sensor[1]) = sample->pressure[1].mbar;
 		if (sample->temperature.mkelvin)
 			entry->temperature = lasttemp = sample->temperature.mkelvin;
 		else
@@ -686,38 +634,6 @@ struct plot_data *populate_plot_entries(struct dive *dive, struct divecomputer *
 
 #undef INSERT_ENTRY
 
-static void populate_cylinder_pressure_data(int idx, int start, int end, struct plot_info *pi, int sensoridx)
-{
-	int i;
-
-	/* First: check that none of the entries has sensor pressure for this cylinder index */
-	for (i = 0; i < pi->nr; i++) {
-		struct plot_data *entry = pi->entry + i;
-		if (entry->sensor[sensoridx] != idx)
-			continue;
-		if (SENSOR_PRESSURE(entry, sensoridx))
-			return;
-	}
-
-	/* Then: populate the first entry with the beginning cylinder pressure */
-	for (i = 0; i < pi->nr; i++) {
-		struct plot_data *entry = pi->entry + i;
-		if (entry->sensor[sensoridx] != idx)
-			continue;
-		SENSOR_PRESSURE(entry, sensoridx) = start;
-		break;
-	}
-
-	/* .. and the last entry with the ending cylinder pressure */
-	for (i = pi->nr; --i >= 0; /* nothing */) {
-		struct plot_data *entry = pi->entry + i;
-		if (entry->sensor[sensoridx] != idx)
-			continue;
-		SENSOR_PRESSURE(entry, sensoridx) = end;
-		break;
-	}
-}
-
 /*
  * Calculate the sac rate between the two plot entries 'first' and 'last'.
  *
@@ -726,6 +642,7 @@ static void populate_cylinder_pressure_data(int idx, int start, int end, struct
  */
 static int sac_between(struct dive *dive, struct plot_data *first, struct plot_data *last)
 {
+	int sensor = 0;
 	int airuse;
 	double pressuretime;
 	pressure_t a, b;
@@ -735,9 +652,9 @@ static int sac_between(struct dive *dive, struct plot_data *first, struct plot_d
 		return 0;
 
 	/* Calculate air use - trivial */
-	a.mbar = GET_PRESSURE(first, 0);
-	b.mbar = GET_PRESSURE(last, 0);
-	cyl = dive->cylinder + first->sensor[0];
+	a.mbar = GET_PRESSURE(first, sensor);
+	b.mbar = GET_PRESSURE(last, sensor);
+	cyl = dive->cylinder + sensor;
 	airuse = gas_volume(cyl, a) - gas_volume(cyl, b);
 	if (airuse <= 0)
 		return 0;
@@ -767,6 +684,7 @@ static void fill_sac(struct dive *dive, struct plot_info *pi, int idx)
 {
 	struct plot_data *entry = pi->entry + idx;
 	struct plot_data *first, *last;
+	int sensor = 0;
 	int time;
 
 	if (entry->sac)
@@ -783,13 +701,12 @@ static void fill_sac(struct dive *dive, struct plot_info *pi, int idx)
 	time = entry->sec - 30;
 	while (idx > 0) {
 		struct plot_data *prev = first-1;
-		if (prev->sensor[0] != first->sensor[0])
-			break;
+
 		if (prev->depth < SURFACE_THRESHOLD && first->depth < SURFACE_THRESHOLD)
 			break;
 		if (prev->sec < time)
 			break;
-		if (!GET_PRESSURE(prev, 0))
+		if (!GET_PRESSURE(prev, sensor))
 			break;
 		idx--;
 		first = prev;
@@ -800,13 +717,11 @@ static void fill_sac(struct dive *dive, struct plot_info *pi, int idx)
 	time = first->sec + 60;
 	while (++idx < pi->nr) {
 		struct plot_data *next = last+1;
-		if (next->sensor[0] != last->sensor[0])
-			break;
 		if (next->depth < SURFACE_THRESHOLD && last->depth < SURFACE_THRESHOLD)
 			break;
 		if (next->sec > time)
 			break;
-		if (!GET_PRESSURE(next, 0))
+		if (!GET_PRESSURE(next, sensor))
 			break;
 		last = next;
 	}
@@ -828,21 +743,66 @@ static void populate_secondary_sensor_data(struct divecomputer *dc, struct plot_
 	/* We should try to see if it has interesting pressure data here */
 }
 
+/*
+ * This adds a pressure entry to the plot_info based on the gas change
+ * information and the manually filled in pressures.
+ */
+static void add_plot_pressure(struct plot_info *pi, int time, int cyl, int mbar)
+{
+	for (int i = 0; i < pi->nr; i++) {
+		struct plot_data *entry = pi->entry + i;
+
+		if (entry->sec < time)
+			continue;
+		SENSOR_PRESSURE(entry, cyl) = mbar;
+		return;
+	}
+}
+
 static void setup_gas_sensor_pressure(struct dive *dive, struct divecomputer *dc, struct plot_info *pi)
 {
-	int i;
+	int prev = -1, i;
+	struct event *ev;
+	unsigned int seen[MAX_CYLINDERS] = { 0, };
+	unsigned int first[MAX_CYLINDERS] = { 0, };
+	unsigned int last[MAX_CYLINDERS] = { 0, };
 	struct divecomputer *secondary;
 
-	/* First, populate the pressures with the manual cylinder data.. */
-	for (i = 0; i < MAX_CYLINDERS; i++) {
-		cylinder_t *cyl = dive->cylinder + i;
-		int start = cyl->start.mbar ?: cyl->sample_start.mbar;
-		int end = cyl->end.mbar ?: cyl->sample_end.mbar;
+	for (ev = get_next_event(dc->events, "gaschange"); ev != NULL; ev = get_next_event(ev->next, "gaschange")) {
+		int cyl = ev->gas.index;
+		int sec = ev->time.seconds;
 
-		if (!start || !end)
+		if (cyl < 0)
 			continue;
 
-		populate_cylinder_pressure_data(i, start, end, pi, dive->cylinder[i].cylinder_use == OXYGEN);
+		if (prev >= 0)
+			last[prev] = sec;
+		prev = cyl;
+
+		last[cyl] = sec;
+		if (!seen[cyl]) {
+			int endtime = sec;
+			if (dc->samples)
+				endtime = dc->sample[dc->samples-1].time.seconds;
+
+			// The end time may be updated by a subsequent cylinder change
+			first[cyl] = sec;
+			last[cyl] = endtime;
+			seen[cyl] = 1;
+		}
+	}
+
+	for (i = 0; i < MAX_CYLINDERS; i++) {
+		if (seen[i]) {
+			cylinder_t *cyl = dive->cylinder + i;
+			int start = cyl->start.mbar;
+			int end = cyl->end.mbar;
+
+			if (start)
+				add_plot_pressure(pi, first[i], i, start);
+			if (end)
+				add_plot_pressure(pi, last[i], i, end);
+		}
 	}
 
 	/*
@@ -862,6 +822,7 @@ static void setup_gas_sensor_pressure(struct dive *dive, struct divecomputer *dc
 /* calculate DECO STOP / TTS / NDL */
 static void calculate_ndl_tts(struct plot_data *entry, struct dive *dive, double surface_pressure)
 {
+	int cylinderindex = 0;
 	/* FIXME: This should be configurable */
 	/* ascent speed up to first deco stop */
 	const int ascent_s_per_step = 1;
@@ -877,7 +838,6 @@ static void calculate_ndl_tts(struct plot_data *entry, struct dive *dive, double
 						    surface_pressure, dive, 1), deco_stepsize);
 	int ascent_depth = entry->depth;
 	/* at what time should we give up and say that we got enuff NDL? */
-	int cylinderindex = entry->sensor[0];
 	/* If iterating through a dive, entry->tts_calc needs to be reset */
 	entry->tts_calc = 0;
 
@@ -971,7 +931,7 @@ void calculate_deco_information(struct dive *dive, struct divecomputer *dc, stru
 			for (j = t0 + time_stepsize; j <= t1; j += time_stepsize) {
 				int depth = interpolate(entry[-1].depth, entry[0].depth, j - t0, t1 - t0);
 				add_segment(depth_to_bar(depth, dive),
-					&dive->cylinder[entry->sensor[0]].gasmix, time_stepsize, entry->o2pressure.mbar, dive, entry->sac);
+					&dive->cylinder[0].gasmix, time_stepsize, entry->o2pressure.mbar, dive, entry->sac);
 				if ((t1 - j < time_stepsize) && (j < t1))
 					time_stepsize = t1 - j;
 			}
@@ -1125,7 +1085,7 @@ static void calculate_gas_information_new(struct dive *dive, struct plot_info *p
 	for (i = 1; i < pi->nr; i++) {
 		int fn2, fhe;
 		struct plot_data *entry = pi->entry + i;
-		int cylinderindex = entry->sensor[0];
+		int cylinderindex = 0;
 
 		amb_pressure = depth_to_bar(entry->depth, dive);
 
@@ -1249,13 +1209,11 @@ void create_plot_info_new(struct dive *dive, struct divecomputer *dc, struct plo
 
 	last_pi_entry_new = populate_plot_entries(dive, dc, pi);
 
-	check_gas_change_events(dive, dc, pi);   /* Populate the gas index from the gas change events */
 	check_setpoint_events(dive, dc, pi);     /* Populate setpoints */
 	setup_gas_sensor_pressure(dive, dc, pi); /* Try to populate our gas pressure knowledge */
 	if (!fast) {
-		populate_pressure_information(dive, dc, pi, false);	/* .. calculate missing pressure entries for all gasses except o2 */
-		if (dc->divemode == CCR)					/* For CCR dives.. */
-			populate_pressure_information(dive, dc, pi, true); /* .. calculate missing o2 gas pressure entries */
+		for (int cyl = 0; cyl < MAX_CYLINDERS; cyl++)
+			populate_pressure_information(dive, dc, pi, cyl);
 	}
 	fill_o2_values(dc, pi, dive);			 /* .. and insert the O2 sensor data having 0 values. */
 	calculate_sac(dive, pi);			 /* Calculate sac */
@@ -1477,7 +1435,6 @@ void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int
 
 	last_sec = start->sec;
 	last_pressure = GET_PRESSURE(start, 0);
-	last_cylidx = start->sensor[0];
 
 	data = start;
 	while (data != stop) {
@@ -1501,10 +1458,6 @@ void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int
 		if (GET_PRESSURE(data, 0) < last_pressure + 2000)
 			bar_used += last_pressure - GET_PRESSURE(data, 0);
 
-		if (data->sensor[0] != last_cylidx)
-			/* if we change tanks, don't try to do SAC rate later */
-			crossed_tankchange = true;
-
 		count += 1;
 		last_sec = data->sec;
 		last_pressure = GET_PRESSURE(data, 0);
@@ -1548,7 +1501,7 @@ void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int
 		pressurevalue = get_pressure_units(bar_used, &pressure_unit);
 		memcpy(buf2, buf, bufsize);
 		snprintf(buf, bufsize, translate("gettextFromC", "%s %sP:%d %s"), buf2, UTF8_DELTA, pressurevalue, pressure_unit);
-		cylinder_t *cyl = displayed_dive.cylinder + start->sensor[0];
+		cylinder_t *cyl = displayed_dive.cylinder + 0;
 		/* if we didn't cross a tank change and know the cylidner size as well, show SAC rate */
 		if (!crossed_tankchange && cyl->type.size.mliter) {
 			double volume_value;
diff --git a/core/profile.h b/core/profile.h
index 988736f3..1cbcd30f 100644
--- a/core/profile.h
+++ b/core/profile.h
@@ -21,15 +21,12 @@ struct divecomputer;
 struct plot_info;
 struct plot_data {
 	unsigned int in_deco : 1;
-	char sensor[2];
 	int sec;
-	/* pressure[0] is main sensor pressure (diluent for CCR)
-	 * pressure[1] is secondary sensor pressure (O2 for CCR)
-	 *
-	 * pressure[x][0] is sensor pressure
+	/*
+	 * pressure[x][0] is sensor pressure for cylinder x
 	 * pressure[x][1] is interpolated pressure
 	 */
-	int pressure[2][2];
+	int pressure[MAX_CYLINDERS][2];
 	int temperature;
 	/* Depth info */
 	int depth;
diff --git a/profile-widget/diveprofileitem.cpp b/profile-widget/diveprofileitem.cpp
index fcaca3a8..6f2bf98b 100644
--- a/profile-widget/diveprofileitem.cpp
+++ b/profile-widget/diveprofileitem.cpp
@@ -683,108 +683,99 @@ void DiveGasPressureItem::modelDataChanged(const QModelIndex &topLeft, const QMo
 	// We don't have enougth data to calculate things, quit.
 	if (!shouldCalculateStuff(topLeft, bottomRight))
 		return;
-	int last_index = -1;
-	QPolygonF boundingPoly, o2Poly; // This is the "Whole Item", but a pressure can be divided in N Polygons.
+
+	int plotted_cyl[MAX_CYLINDERS] = { false, };
+	int last_plotted[MAX_CYLINDERS] = { 0, };
+	QPolygonF poly[MAX_CYLINDERS];
+	QPolygonF boundingPoly;
 	polygons.clear();
-	polygons.append(o2Poly);
 
 	for (int i = 0, count = dataModel->rowCount(); i < count; i++) {
-		plot_data *entry = dataModel->data().entry + i;
-		int mbar = GET_PRESSURE(entry, 0);
-		int o2mbar = GET_PRESSURE(entry, 1);
+		struct plot_data *entry = dataModel->data().entry + i;
 
-		if ((int)entry->sensor[0] != last_index) {
-			polygons.append(QPolygonF()); // this is the polygon that will be actually drawn on screen.
-			last_index = entry->sensor[0];
-		}
-		if (!mbar) {
-			continue;
-		}
-		if (o2mbar) {
-			QPointF o2point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(o2mbar));
-			boundingPoly.push_back(o2point);
-			polygons.first().push_back(o2point);
+		for (int cyl = 0; cyl < MAX_CYLINDERS; cyl++) {
+			int mbar = GET_PRESSURE(entry, cyl);
+			int time = entry->sec;
+
+			if (!mbar)
+				continue;
+
+			QPointF point(hAxis->posAtValue(time), vAxis->posAtValue(mbar));
+			boundingPoly.push_back(point);
+
+			if (plotted_cyl[cyl]) {
+				/* Have we used this culinder in the last two minutes? Continue */
+				if (time - last_plotted[cyl] <= 2*60) {
+					poly[cyl].push_back(point);
+					last_plotted[cyl] = time;
+					continue;
+				}
+
+				/* Finish the previous one, start a new one */
+				polygons.append(poly[cyl]);
+				poly[cyl] = QPolygonF();
+			}
+
+			plotted_cyl[cyl] = true;
+			last_plotted[cyl] = time;
+			poly[cyl].push_back(point);
 		}
+	}
 
-		QPointF point(hAxis->posAtValue(entry->sec), vAxis->posAtValue(mbar));
-		boundingPoly.push_back(point);    // The BoundingRect
-		polygons.last().push_back(point); // The polygon thta will be plotted.
+	for (int cyl = 0; cyl < MAX_CYLINDERS; cyl++) {
+		if (!plotted_cyl[cyl])
+			continue;
+		polygons.append(poly[cyl]);
 	}
+
 	setPolygon(boundingPoly);
 	qDeleteAll(texts);
 	texts.clear();
-	int mbar, cyl, lastcyl;
-	int o2mbar, o2cyl;
+
 	int seen_cyl[MAX_CYLINDERS] = { false, };
 	int last_pressure[MAX_CYLINDERS] = { 0, };
 	int last_time[MAX_CYLINDERS] = { 0, };
-	struct plot_data *entry;
-
-	cyl = o2cyl = lastcyl = -1;
-	mbar = o2mbar = 0;
 
 	double print_y_offset[8][2] = { { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 } ,{ 0, -0.5 }, { 0, -0.5 }, { 0, -0.5 } };
-	// CCR dives: These are offset values used to print the gas lables and pressures on a CCR dive profile at
-	// appropriate Y-coordinates: One doublet of values for each of 8 cylinders.
+	// These are offset values used to print the gas lables and pressures on a
+	// dive profile at appropriate Y-coordinates: One doublet of values for each
+	// of 8 cylinders.
 	// Order of offsets within a doublet: gas lable offset; gas pressure offset.
 	// The array is initialised with default values that apply to non-CCR dives.
 
-	bool offsets_initialised = false;
-	QFlags<Qt::AlignmentFlag> alignVar= Qt::AlignTop, align_dil = Qt::AlignBottom, align_o2 = Qt::AlignTop;
+	QFlags<Qt::AlignmentFlag> alignVar = Qt::AlignTop;
+	QFlags<Qt::AlignmentFlag> align[MAX_CYLINDERS];
+
 	double axisRange = (vAxis->maximum() - vAxis->minimum())/1000;	// Convert axis pressure range to bar
 	double axisLog = log10(log10(axisRange));
+
 	for (int i = 0, count = dataModel->rowCount(); i < count; i++) {
-		entry = dataModel->data().entry + i;
-
-		cyl = entry->sensor[0];
-		o2cyl = entry->sensor[1];
-		mbar = GET_PRESSURE(entry, 0);
-		o2mbar = GET_PRESSURE(entry, 1);
-
-		if (o2mbar) {	//  Do we have a second cylinder pressure? If so, do
-				// The first time an o2 value is detected, see if the oxygen cyl pressure graph starts above or below the dil graph
-			if (!offsets_initialised) {	// Initialise the parameters for placing the text correctly near the graph line:
-				if ((o2mbar > mbar)) {	// If above, write o2 start cyl pressure above graph and diluent pressure below graph:
-					print_y_offset[o2cyl][0] = -7 * axisLog; // y offset for oxygen gas lable (above); pressure offsets=-0.5, already initialised
-					print_y_offset[cyl][0] = 5 * axisLog; // y offset for diluent gas lable (below)
-				} else {				// ... else write o2 start cyl pressure below graph:
-					print_y_offset[o2cyl][0]  = 5 * axisLog; // o2 lable & pressure below graph; pressure offsets=-0.5, already initialised
-					print_y_offset[cyl][0] = -7.8 * axisLog;  // and diluent lable above graph.
-					align_dil = Qt::AlignTop;
-					align_o2  = Qt::AlignBottom;
-				}
-				offsets_initialised = true;
-			}
+		struct plot_data *entry = dataModel->data().entry + i;
 
-			if (!seen_cyl[o2cyl]) {	//For o2, on the left of profile, write lable and pressure
-				plotPressureValue(o2mbar, entry->sec, align_o2, print_y_offset[o2cyl][1]);
-				plotGasValue(o2mbar, entry->sec, displayed_dive.cylinder[o2cyl].gasmix, align_o2, print_y_offset[o2cyl][0]);
-				seen_cyl[o2cyl] = true;
-			}
-			last_pressure[o2cyl] = o2mbar;
-			last_time[o2cyl] = entry->sec;
-			alignVar = align_dil;
-		}
+		for (int cyl = 0; cyl < MAX_CYLINDERS; cyl++) {
+			int mbar = GET_PRESSURE(entry, cyl);
 
-		if (!mbar)
-			continue;
+			if (!mbar)
+				continue;
 
-		if (cyl != lastcyl) {		// Pressure value near the left hand edge of the profile - other cylinders:
-			lastcyl = cyl;		// For each other cylinder, write the gas lable and pressure
 			if (!seen_cyl[cyl]) {
 				plotPressureValue(mbar, entry->sec, alignVar, print_y_offset[cyl][1]);
-				plotGasValue(mbar, entry->sec, displayed_dive.cylinder[cyl].gasmix, align_dil, print_y_offset[cyl][0]);
+				plotGasValue(mbar, entry->sec, displayed_dive.cylinder[cyl].gasmix, alignVar, print_y_offset[cyl][0]);
 				seen_cyl[cyl] = true;
+
+				/* Alternate alignment as we see cylinder use.. */
+				align[cyl] = alignVar;
+				alignVar ^= Qt::AlignTop | Qt::AlignBottom;
 			}
+			last_pressure[cyl] = mbar;
+			last_time[cyl] = entry->sec;
 		}
-		last_pressure[cyl] = mbar;
-		last_time[cyl] = entry->sec;
 	}
 
-	for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) {	// For each cylinder, on right hand side of profile, write cylinder pressure
-		alignVar = ((o2cyl >= 0) && (cyl == o2cyl)) ? align_o2 : align_dil;
+	// For each cylinder, on right hand side of profile, write cylinder pressure
+	for (int cyl = 0; cyl < MAX_CYLINDERS; cyl++) {
 		if (last_time[cyl]) {
-			plotPressureValue(last_pressure[cyl], last_time[cyl], (alignVar | Qt::AlignLeft), print_y_offset[cyl][1]);
+			plotPressureValue(last_pressure[cyl], last_time[cyl], align[cyl] | Qt::AlignLeft, print_y_offset[cyl][1]);
 		}
 	}
 }
diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp
index 17f3544f..d8fd8da2 100644
--- a/profile-widget/profilewidget2.cpp
+++ b/profile-widget/profilewidget2.cpp
@@ -1407,8 +1407,6 @@ void ProfileWidget2::contextMenuEvent(QContextMenuEvent *event)
 			action->setText(model->data(model->index(i, 0), Qt::DisplayRole).toString() + QString(tr(" (Tank %1)")).arg(i + 1));
 			connect(action, SIGNAL(triggered(bool)), this, SLOT(changeGas()));
 			action->setData(event->globalPos());
-			if (i == entry->sensor[0])
-				action->setDisabled(true);
 			gasChange->addAction(action);
 		}
 	}
diff --git a/profile-widget/tankitem.cpp b/profile-widget/tankitem.cpp
index 89ddffed..e2b528eb 100644
--- a/profile-widget/tankitem.cpp
+++ b/profile-widget/tankitem.cpp
@@ -98,20 +98,24 @@ void TankItem::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &b
 	// walk the list and figure out which tanks go where
 	struct plot_data *entry = pInfoEntry;
 	struct plot_data *lastentry = pInfoEntry;
-	int cylIdx = entry->sensor[0];
+	int cylIdx = 0; // explicit_first_cylinder(dive, dc)
 	int i = -1;
 	int startTime = 0;
 	struct gasmix *gas = &diveCylinderStore.cylinder[cylIdx].gasmix;
 	qreal width, left;
+
+	// FIXME! This used to depend on the sensor indexes that we no longer have
+	// We should use gaschange events or something
 	while (++i < pInfoNr) {
+		int newIdx = 0;	// get_next_event(dc->events, "gaschange");
 		entry = &pInfoEntry[i];
 		lastentry = &pInfoEntry[i-1];
-		if (entry->sensor[0] == cylIdx)
+		if (newIdx == cylIdx)
 			continue;
 		width = hAxis->posAtValue(lastentry->sec) - hAxis->posAtValue(startTime);
 		left = hAxis->posAtValue(startTime);
 		createBar(left, width, gas);
-		cylIdx = entry->sensor[0];
+		cylIdx = newIdx;
 		gas = &diveCylinderStore.cylinder[cylIdx].gasmix;
 		startTime = lastentry->sec;
 	}
diff --git a/qt-models/diveplotdatamodel.cpp b/qt-models/diveplotdatamodel.cpp
index b4e72b2d..3e0ae57f 100644
--- a/qt-models/diveplotdatamodel.cpp
+++ b/qt-models/diveplotdatamodel.cpp
@@ -40,7 +40,7 @@ QVariant DivePlotDataModel::data(const QModelIndex &index, int role) const
 		case USERENTERED:
 			return false;
 		case CYLINDERINDEX:
-			return item.sensor[0];
+			return 0;
 		case SENSOR_PRESSURE:
 			return item.pressure[0][0];
 		case INTERPOLATED_PRESSURE:


More information about the subsurface mailing list