summaryrefslogtreecommitdiffstats
path: root/core/gaspressures.c
blob: 3205a00f6ed011234f34432428b5d615f6978453 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
// SPDX-License-Identifier: GPL-2.0
/*  gaspressures.c
 *  ---------------
 *  This file contains the routines to calculate the gas pressures in the cylinders.
 *  The functions below support the code in profile.c.
 *  The high-level function is populate_pressure_information(), called by function
 *  create_plot_info_new() in profile.c. The other functions below are, in turn,
 *  called by populate_pressure_information(). The calling sequence is as follows:
 *
 *  populate_pressure_information() -> calc_pressure_time()
 *                                  -> fill_missing_tank_pressures() -> fill_missing_segment_pressures()
 *                                                                   -> get_pr_interpolate_data()
 *
 *  The pr_track_t related functions below implement a linked list that is used by
 *  the majority of the functions below. The linked list covers a part of the dive profile
 *  for which there are no cylinder pressure data. Each element in the linked list
 *  represents a segment between two consecutive points on the dive profile.
 *  pr_track_t is defined in gaspressures.h
 */

#include "dive.h"
#include "display.h"
#include "profile.h"
#include "gaspressures.h"

static pr_track_t *pr_track_alloc(int start, int t_start)
{
	pr_track_t *pt = malloc(sizeof(pr_track_t));
	pt->start = start;
	pt->end = 0;
	pt->t_start = pt->t_end = t_start;
	pt->pressure_time = 0;
	pt->next = NULL;
	return pt;
}

/* poor man's linked list */
static pr_track_t *list_last(pr_track_t *list)
{
	pr_track_t *tail = list;
	if (!tail)
		return NULL;
	while (tail->next) {
		tail = tail->next;
	}
	return tail;
}

static pr_track_t *list_add(pr_track_t *list, pr_track_t *element)
{
	pr_track_t *tail = list_last(list);
	if (!tail)
		return element;
	tail->next = element;
	return list;
}

static void list_free(pr_track_t *list)
{
	if (!list)
		return;
	list_free(list->next);
	free(list);
}

#ifdef DEBUG_PR_TRACK
static void dump_pr_track(int cyl, pr_track_t *track_pr)
{
	pr_track_t *list;

	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

/*
 * This looks at the pressures for one cylinder, and
 * calculates any missing beginning/end pressures for
 * each segment by taking the over-all SAC-rate into
 * account for that cylinder.
 *
 * NOTE! Many segments have full pressure information
 * (both beginning and ending pressure). But if we have
 * switched away from a cylinder, we will have the
 * beginning pressure for the first segment with a
 * missing end pressure. We may then have one or more
 * segments without beginning or end pressures, until
 * we finally have a segment with an end pressure.
 *
 * We want to spread out the pressure over these missing
 * segments according to how big of a time_pressure area
 * they have.
 */
static void fill_missing_segment_pressures(pr_track_t *list, enum interpolation_strategy strategy)
{
	double magic;

	while (list) {
		int start = list->start, end;
		pr_track_t *tmp = list;
		int pt_sum = 0, pt = 0;

		for (;;) {
			pt_sum += tmp->pressure_time;
			end = tmp->end;
			if (end)
				break;
			end = start;
			if (!tmp->next)
				break;
			tmp = tmp->next;
		}

		if (!start)
			start = end;

		/*
		 * Now 'start' and 'end' contain the pressure values
		 * for the set of segments described by 'list'..'tmp'.
		 * pt_sum is the sum of all the pressure-times of the
		 * segments.
		 *
		 * Now dole out the pressures relative to pressure-time.
		 */
		list->start = start;
		tmp->end = end;
		switch (strategy) {
		case SAC:
			for (;;) {
				int pressure;
				pt += list->pressure_time;
				pressure = start;
				if (pt_sum)
					pressure -= lrint((start - end) * (double)pt / pt_sum);
				list->end = pressure;
				if (list == tmp)
					break;
				list = list->next;
				list->start = pressure;
			}
			break;
		case TIME:
			if (list->t_end && (tmp->t_start - tmp->t_end)) {
				magic = (list->t_start - tmp->t_end) / (tmp->t_start - tmp->t_end);
				list->end = lrint(start - (start - end) * magic);
			} else {
				list->end = start;
			}
			break;
		case CONSTANT:
			list->end = start;
		}

		/* Ok, we've done that set of segments */
		list = list->next;
	}
}

#ifdef DEBUG_PR_INTERPOLATE
void dump_pr_interpolate(int i, pr_interpolate_t interpolate_pr)
{
	printf("Interpolate for entry %d: start %d - end %d - pt %d - acc_pt %d\n", i,
	       interpolate_pr.start, interpolate_pr.end, interpolate_pr.pressure_time, interpolate_pr.acc_pressure_time);
}
#endif


static struct pr_interpolate_struct get_pr_interpolate_data(pr_track_t *segment, struct plot_info *pi, int cur)
{ // cur = index to pi->entry corresponding to t_end of segment;
	struct pr_interpolate_struct interpolate;
	int i;
	struct plot_data *entry;

	interpolate.start = segment->start;
	interpolate.end = segment->end;
	interpolate.acc_pressure_time = 0;
	interpolate.pressure_time = 0;

	for (i = 0; i < pi->nr; i++) {
		entry = pi->entry + i;

		if (entry->sec < segment->t_start)
			continue;
		interpolate.pressure_time += entry->pressure_time;
		if (entry->sec >= segment->t_end)
			break;
		if (i <= cur)
			interpolate.acc_pressure_time += entry->pressure_time;
	}
	return interpolate;
}

static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, pr_track_t *track_pr, int cyl)
{
	int i;
	struct plot_data *entry;
	pr_interpolate_t interpolate = { 0, 0, 0, 0 };
	pr_track_t *last_segment = NULL;
	int cur_pr;
	enum interpolation_strategy strategy;

	/* 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
	dump_pr_track(cyl, track_pr);
#endif

	/* Transfer interpolated cylinder pressures from pr_track strucktures to plotdata
	 * Go down the list of tank pressures in plot_info. Align them with the start &
	 * end times of each profile segment represented by a pr_track_t structure. Get
	 * the accumulated pressure_depths from the pr_track_t structures and then
	 * interpolate the pressure where these do not exist in the plot_info pressure
	 * variables. Pressure values are transferred from the pr_track_t structures
	 * to the plot_info structure, allowing us to plot the tank pressure.
	 *
	 * The first two pi structures are "fillers", but in case we don't have a sample
	 * at time 0 we need to process the second of them here, therefore i=1 */
	for (i = 1; i < pi->nr; i++) { // For each point on the profile:
		double magic;
		pr_track_t *segment;
		int pressure;
		int *save_pressure, *save_interpolated;

		entry = pi->entry + i;

		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 = 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;
		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)

		// 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 ..
		if (segment == last_segment) {
			interpolate.acc_pressure_time += entry->pressure_time;
		} else {
			// Set up an interpolation structure
			interpolate = get_pr_interpolate_data(segment, pi, i);
			last_segment = segment;
		}

		if(dive->cylinder[cyl].cylinder_use == OC_GAS) {

			/* if this segment has pressure_time, then calculate a new interpolated pressure */
			if (interpolate.pressure_time) {
				/* Overall pressure change over total pressure-time for this segment*/
				magic = (interpolate.end - interpolate.start) / (double)interpolate.pressure_time;

				/* Use that overall pressure change to update the current pressure */
				cur_pr = lrint(interpolate.start + magic * interpolate.acc_pressure_time);
			}
		} else {
			magic = (interpolate.end - interpolate.start) /  (segment->t_end - segment->t_start);
			cur_pr = lrint(segment->start + magic * (entry->sec - segment->t_start));
		}
		*save_interpolated = cur_pr; // and store the interpolated data in plot_info
	}
}


/*
 * What's the pressure-time between two plot data entries?
 * We're calculating the integral of pressure over time by
 * adding these up.
 *
 * The units won't matter as long as everybody agrees about
 * them, since they'll cancel out - we use this to calculate
 * a constant SAC-rate-equivalent, but we only use it to
 * scale pressures, so it ends up being a unitless scaling
 * factor.
 */
static inline int calc_pressure_time(struct dive *dive, struct plot_data *a, struct plot_data *b)
{
	int time = b->sec - a->sec;
	int depth = (a->depth + b->depth) / 2;

	if (depth <= SURFACE_THRESHOLD)
		return 0;

	return depth_to_mbar(depth, dive) * time;
}

#ifdef PRINT_PRESSURES_DEBUG
// A CCR debugging tool that prints the gas pressures in cylinder 0 and in the diluent cylinder, used in populate_pressure_information():
static void debug_print_pressures(struct plot_info *pi)
{
	int i;
	for (i = 0; i < pi->nr; i++) {
		struct plot_data *entry = pi->entry + i;
		printf("%5d |%9d | %9d || %9d | %9d |\n", i, SENSOR_PRESSURE(entry), INTERPOLATED_PRESSURE(entry), DILUENT_PRESSURE(entry), INTERPOLATED_DILUENT_PRESSURE(entry));
	}
}
#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
 * tank pressure it creates a pr_track_alloc structure that represents a segment on the dive profile and that
 * contains tank pressures. There is a linked list of pr_track_alloc structures for each cylinder. These pr_track_alloc
 * structures ultimately allow for filling the missing tank pressure values on the dive profile using the depth_pressure
 * of the dive. To do this, it calculates the summed pressure-time value for the duration of the dive and stores these
 * 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 sensor)
{
	(void) dc;
	int first, last, cyl;
	cylinder_t *cylinder = dive->cylinder + sensor;
	pr_track_t *track = NULL;
	pr_track_t *current = NULL;
	struct event *ev;
	int missing_pr = 0, dense = 1;

	/* if we have no pressure data whatsoever, this is pointless, so let's just return */
	if (!cylinder->start.mbar && !cylinder->end.mbar &&
	    !cylinder->sample_start.mbar && !cylinder->sample_end.mbar)
		return;

	/* 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 = SENSOR_PRESSURE(entry, sensor);

		if (!pressure)
			continue;
		if (first < 0)
			first = i;
		last = i;
	}

	/* 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);
		unsigned 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 (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;
		}

		// 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 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 (current && !pressure) {
			missing_pr = 1;
			dense = 0;
			continue;
		}

		// 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;

		// 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);
		track = list_add(track, current);
		dense = 1;
	}

	if (missing_pr) {
		fill_missing_tank_pressures(dive, pi, track, sensor);
	}

#ifdef PRINT_PRESSURES_DEBUG
	debug_print_pressures(pi);
#endif

	list_free(track);
}