aboutsummaryrefslogtreecommitdiffstats
path: root/subsurface-core/planner.c
diff options
context:
space:
mode:
Diffstat (limited to 'subsurface-core/planner.c')
-rw-r--r--subsurface-core/planner.c1471
1 files changed, 0 insertions, 1471 deletions
diff --git a/subsurface-core/planner.c b/subsurface-core/planner.c
deleted file mode 100644
index 705aad1cb..000000000
--- a/subsurface-core/planner.c
+++ /dev/null
@@ -1,1471 +0,0 @@
-/* planner.c
- *
- * code that allows us to plan future dives
- *
- * (c) Dirk Hohndel 2013
- */
-#include <assert.h>
-#include <unistd.h>
-#include <ctype.h>
-#include <string.h>
-#include "dive.h"
-#include "deco.h"
-#include "divelist.h"
-#include "planner.h"
-#include "gettext.h"
-#include "libdivecomputer/parser.h"
-
-#define TIMESTEP 2 /* second */
-#define DECOTIMESTEP 60 /* seconds. Unit of deco stop times */
-
-int decostoplevels_metric[] = { 0, 3000, 6000, 9000, 12000, 15000, 18000, 21000, 24000, 27000,
- 30000, 33000, 36000, 39000, 42000, 45000, 48000, 51000, 54000, 57000,
- 60000, 63000, 66000, 69000, 72000, 75000, 78000, 81000, 84000, 87000,
- 90000, 100000, 110000, 120000, 130000, 140000, 150000, 160000, 170000,
- 180000, 190000, 200000, 220000, 240000, 260000, 280000, 300000,
- 320000, 340000, 360000, 380000 };
-int decostoplevels_imperial[] = { 0, 3048, 6096, 9144, 12192, 15240, 18288, 21336, 24384, 27432,
- 30480, 33528, 36576, 39624, 42672, 45720, 48768, 51816, 54864, 57912,
- 60960, 64008, 67056, 70104, 73152, 76200, 79248, 82296, 85344, 88392,
- 91440, 101600, 111760, 121920, 132080, 142240, 152400, 162560, 172720,
- 182880, 193040, 203200, 223520, 243840, 264160, 284480, 304800,
- 325120, 345440, 365760, 386080 };
-
-double plangflow, plangfhigh;
-bool plan_verbatim, plan_display_runtime, plan_display_duration, plan_display_transitions;
-
-pressure_t first_ceiling_pressure, max_bottom_ceiling_pressure = {};
-
-const char *disclaimer;
-
-#if DEBUG_PLAN
-void dump_plan(struct diveplan *diveplan)
-{
- struct divedatapoint *dp;
- struct tm tm;
-
- if (!diveplan) {
- printf("Diveplan NULL\n");
- return;
- }
- utc_mkdate(diveplan->when, &tm);
-
- printf("\nDiveplan @ %04d-%02d-%02d %02d:%02d:%02d (surfpres %dmbar):\n",
- tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
- tm.tm_hour, tm.tm_min, tm.tm_sec,
- diveplan->surface_pressure);
- dp = diveplan->dp;
- while (dp) {
- printf("\t%3u:%02u: %dmm gas: %d o2 %d h2\n", FRACTION(dp->time, 60), dp->depth, get_o2(&dp->gasmix), get_he(&dp->gasmix));
- dp = dp->next;
- }
-}
-#endif
-
-bool diveplan_empty(struct diveplan *diveplan)
-{
- struct divedatapoint *dp;
- if (!diveplan || !diveplan->dp)
- return true;
- dp = diveplan->dp;
- while (dp) {
- if (dp->time)
- return false;
- dp = dp->next;
- }
- return true;
-}
-
-/* get the gas at a certain time during the dive */
-void get_gas_at_time(struct dive *dive, struct divecomputer *dc, duration_t time, struct gasmix *gas)
-{
- // we always start with the first gas, so that's our gas
- // unless an event tells us otherwise
- struct event *event = dc->events;
- *gas = dive->cylinder[0].gasmix;
- while (event && event->time.seconds <= time.seconds) {
- if (!strcmp(event->name, "gaschange")) {
- int cylinder_idx = get_cylinder_index(dive, event);
- *gas = dive->cylinder[cylinder_idx].gasmix;
- }
- event = event->next;
- }
-}
-
-int get_gasidx(struct dive *dive, struct gasmix *mix)
-{
- int gasidx = -1;
-
- while (++gasidx < MAX_CYLINDERS)
- if (gasmix_distance(&dive->cylinder[gasidx].gasmix, mix) < 100)
- return gasidx;
- return -1;
-}
-
-void interpolate_transition(struct dive *dive, duration_t t0, duration_t t1, depth_t d0, depth_t d1, const struct gasmix *gasmix, o2pressure_t po2)
-{
- uint32_t j;
-
- for (j = t0.seconds; j < t1.seconds; j++) {
- int depth = interpolate(d0.mm, d1.mm, j - t0.seconds, t1.seconds - t0.seconds);
- add_segment(depth_to_bar(depth, dive), gasmix, 1, po2.mbar, dive, prefs.bottomsac);
- }
- if (d1.mm > d0.mm)
- calc_crushing_pressure(depth_to_bar(d1.mm, &displayed_dive));
-}
-
-/* returns the tissue tolerance at the end of this (partial) dive */
-void tissue_at_end(struct dive *dive, char **cached_datap)
-{
- struct divecomputer *dc;
- struct sample *sample, *psample;
- int i;
- depth_t lastdepth = {};
- duration_t t0 = {}, t1 = {};
- struct gasmix gas;
-
- if (!dive)
- return;
- if (*cached_datap) {
- restore_deco_state(*cached_datap);
- } else {
- init_decompression(dive);
- cache_deco_state(cached_datap);
- }
- dc = &dive->dc;
- if (!dc->samples)
- return;
- psample = sample = dc->sample;
-
- for (i = 0; i < dc->samples; i++, sample++) {
- o2pressure_t setpoint;
-
- if (i)
- setpoint = sample[-1].setpoint;
- else
- setpoint = sample[0].setpoint;
-
- t1 = sample->time;
- get_gas_at_time(dive, dc, t0, &gas);
- if (i > 0)
- lastdepth = psample->depth;
-
- /* The ceiling in the deeper portion of a multilevel dive is sometimes critical for the VPM-B
- * Boyle's law compensation. We should check the ceiling prior to ascending during the bottom
- * portion of the dive. The maximum ceiling might be reached while ascending, but testing indicates
- * that it is only marginally deeper than the ceiling at the start of ascent.
- * Do not set the first_ceiling_pressure variable (used for the Boyle's law compensation calculation)
- * at this stage, because it would interfere with calculating the ceiling at the end of the bottom
- * portion of the dive.
- * Remember the value for later.
- */
- if ((prefs.deco_mode == VPMB) && (lastdepth.mm > sample->depth.mm)) {
- pressure_t ceiling_pressure;
- nuclear_regeneration(t0.seconds);
- vpmb_start_gradient();
- ceiling_pressure.mbar = depth_to_mbar(deco_allowed_depth(tissue_tolerance_calc(dive,
- depth_to_bar(lastdepth.mm, dive)),
- dive->surface_pressure.mbar / 1000.0,
- dive,
- 1),
- dive);
- if (ceiling_pressure.mbar > max_bottom_ceiling_pressure.mbar)
- max_bottom_ceiling_pressure.mbar = ceiling_pressure.mbar;
- }
-
- interpolate_transition(dive, t0, t1, lastdepth, sample->depth, &gas, setpoint);
- psample = sample;
- t0 = t1;
- }
-}
-
-
-/* if a default cylinder is set, use that */
-void fill_default_cylinder(cylinder_t *cyl)
-{
- const char *cyl_name = prefs.default_cylinder;
- struct tank_info_t *ti = tank_info;
- pressure_t pO2 = {.mbar = 1600};
-
- if (!cyl_name)
- return;
- while (ti->name != NULL) {
- if (strcmp(ti->name, cyl_name) == 0)
- break;
- ti++;
- }
- if (ti->name == NULL)
- /* didn't find it */
- return;
- cyl->type.description = strdup(ti->name);
- if (ti->ml) {
- cyl->type.size.mliter = ti->ml;
- cyl->type.workingpressure.mbar = ti->bar * 1000;
- } else {
- cyl->type.workingpressure.mbar = psi_to_mbar(ti->psi);
- if (ti->psi)
- cyl->type.size.mliter = cuft_to_l(ti->cuft) * 1000 / bar_to_atm(psi_to_bar(ti->psi));
- }
- // MOD of air
- cyl->depth = gas_mod(&cyl->gasmix, pO2, &displayed_dive, 1);
-}
-
-/* make sure that the gas we are switching to is represented in our
- * list of cylinders */
-static int verify_gas_exists(struct gasmix mix_in)
-{
- int i;
- cylinder_t *cyl;
-
- for (i = 0; i < MAX_CYLINDERS; i++) {
- cyl = displayed_dive.cylinder + i;
- if (cylinder_nodata(cyl))
- continue;
- if (gasmix_distance(&cyl->gasmix, &mix_in) < 100)
- return i;
- }
- fprintf(stderr, "this gas %s should have been on the cylinder list\nThings will fail now\n", gasname(&mix_in));
- return -1;
-}
-
-/* calculate the new end pressure of the cylinder, based on its current end pressure and the
- * latest segment. */
-static void update_cylinder_pressure(struct dive *d, int old_depth, int new_depth, int duration, int sac, cylinder_t *cyl, bool in_deco)
-{
- volume_t gas_used;
- pressure_t delta_p;
- depth_t mean_depth;
- int factor = 1000;
-
- if (d->dc.divemode == PSCR)
- factor = prefs.pscr_ratio;
-
- if (!cyl)
- return;
- mean_depth.mm = (old_depth + new_depth) / 2;
- gas_used.mliter = depth_to_atm(mean_depth.mm, d) * sac / 60 * duration * factor / 1000;
- cyl->gas_used.mliter += gas_used.mliter;
- if (in_deco)
- cyl->deco_gas_used.mliter += gas_used.mliter;
- if (cyl->type.size.mliter) {
- delta_p.mbar = gas_used.mliter * 1000.0 / cyl->type.size.mliter;
- cyl->end.mbar -= delta_p.mbar;
- }
-}
-
-/* simply overwrite the data in the displayed_dive
- * return false if something goes wrong */
-static void create_dive_from_plan(struct diveplan *diveplan, bool track_gas)
-{
- struct divedatapoint *dp;
- struct divecomputer *dc;
- struct sample *sample;
- struct gasmix oldgasmix;
- struct event *ev;
- cylinder_t *cyl;
- int oldpo2 = 0;
- int lasttime = 0;
- int lastdepth = 0;
- enum dive_comp_type type = displayed_dive.dc.divemode;
-
- if (!diveplan || !diveplan->dp)
- return;
-#if DEBUG_PLAN & 4
- printf("in create_dive_from_plan\n");
- dump_plan(diveplan);
-#endif
- displayed_dive.salinity = diveplan->salinity;
- // reset the cylinders and clear out the samples and events of the
- // displayed dive so we can restart
- reset_cylinders(&displayed_dive, track_gas);
- dc = &displayed_dive.dc;
- dc->when = displayed_dive.when = diveplan->when;
- free(dc->sample);
- dc->sample = NULL;
- dc->samples = 0;
- dc->alloc_samples = 0;
- while ((ev = dc->events)) {
- dc->events = dc->events->next;
- free(ev);
- }
- dp = diveplan->dp;
- cyl = &displayed_dive.cylinder[0];
- oldgasmix = cyl->gasmix;
- sample = prepare_sample(dc);
- sample->setpoint.mbar = dp->setpoint;
- sample->sac.mliter = prefs.bottomsac;
- oldpo2 = dp->setpoint;
- if (track_gas && cyl->type.workingpressure.mbar)
- sample->cylinderpressure.mbar = cyl->end.mbar;
- sample->manually_entered = true;
- finish_sample(dc);
- while (dp) {
- struct gasmix gasmix = dp->gasmix;
- int po2 = dp->setpoint;
- if (dp->setpoint)
- type = CCR;
- int time = dp->time;
- int depth = dp->depth;
-
- if (time == 0) {
- /* special entries that just inform the algorithm about
- * additional gases that are available */
- if (verify_gas_exists(gasmix) < 0)
- goto gas_error_exit;
- dp = dp->next;
- continue;
- }
-
- /* Check for SetPoint change */
- if (oldpo2 != po2) {
- /* this is a bad idea - we should get a different SAMPLE_EVENT type
- * reserved for this in libdivecomputer... overloading SMAPLE_EVENT_PO2
- * with a different meaning will only cause confusion elsewhere in the code */
- add_event(dc, lasttime, SAMPLE_EVENT_PO2, 0, po2, "SP change");
- oldpo2 = po2;
- }
-
- /* Make sure we have the new gas, and create a gas change event */
- if (gasmix_distance(&gasmix, &oldgasmix) > 0) {
- int idx;
- if ((idx = verify_gas_exists(gasmix)) < 0)
- goto gas_error_exit;
- /* need to insert a first sample for the new gas */
- add_gas_switch_event(&displayed_dive, dc, lasttime + 1, idx);
- cyl = &displayed_dive.cylinder[idx];
- sample = prepare_sample(dc);
- sample[-1].setpoint.mbar = po2;
- sample->time.seconds = lasttime + 1;
- sample->depth.mm = lastdepth;
- sample->manually_entered = dp->entered;
- sample->sac.mliter = dp->entered ? prefs.bottomsac : prefs.decosac;
- if (track_gas && cyl->type.workingpressure.mbar)
- sample->cylinderpressure.mbar = cyl->sample_end.mbar;
- finish_sample(dc);
- oldgasmix = gasmix;
- }
- /* Create sample */
- sample = prepare_sample(dc);
- /* set po2 at beginning of this segment */
- /* and keep it valid for last sample - where it likely doesn't matter */
- sample[-1].setpoint.mbar = po2;
- sample->setpoint.mbar = po2;
- sample->time.seconds = lasttime = time;
- sample->depth.mm = lastdepth = depth;
- sample->manually_entered = dp->entered;
- sample->sac.mliter = dp->entered ? prefs.bottomsac : prefs.decosac;
- if (track_gas && !sample[-1].setpoint.mbar) { /* Don't track gas usage for CCR legs of dive */
- update_cylinder_pressure(&displayed_dive, sample[-1].depth.mm, depth, time - sample[-1].time.seconds,
- dp->entered ? diveplan->bottomsac : diveplan->decosac, cyl, !dp->entered);
- if (cyl->type.workingpressure.mbar)
- sample->cylinderpressure.mbar = cyl->end.mbar;
- }
- finish_sample(dc);
- dp = dp->next;
- }
- dc->divemode = type;
-#if DEBUG_PLAN & 32
- save_dive(stdout, &displayed_dive);
-#endif
- return;
-
-gas_error_exit:
- report_error(translate("gettextFromC", "Too many gas mixes"));
- return;
-}
-
-void free_dps(struct diveplan *diveplan)
-{
- if (!diveplan)
- return;
- struct divedatapoint *dp = diveplan->dp;
- while (dp) {
- struct divedatapoint *ndp = dp->next;
- free(dp);
- dp = ndp;
- }
- diveplan->dp = NULL;
-}
-
-struct divedatapoint *create_dp(int time_incr, int depth, struct gasmix gasmix, int po2)
-{
- struct divedatapoint *dp;
-
- dp = malloc(sizeof(struct divedatapoint));
- dp->time = time_incr;
- dp->depth = depth;
- dp->gasmix = gasmix;
- dp->setpoint = po2;
- dp->entered = false;
- dp->next = NULL;
- return dp;
-}
-
-void add_to_end_of_diveplan(struct diveplan *diveplan, struct divedatapoint *dp)
-{
- struct divedatapoint **lastdp = &diveplan->dp;
- struct divedatapoint *ldp = *lastdp;
- int lasttime = 0;
- while (*lastdp) {
- ldp = *lastdp;
- if (ldp->time > lasttime)
- lasttime = ldp->time;
- lastdp = &(*lastdp)->next;
- }
- *lastdp = dp;
- if (ldp && dp->time != 0)
- dp->time += lasttime;
-}
-
-struct divedatapoint *plan_add_segment(struct diveplan *diveplan, int duration, int depth, struct gasmix gasmix, int po2, bool entered)
-{
- struct divedatapoint *dp = create_dp(duration, depth, gasmix, po2);
- dp->entered = entered;
- add_to_end_of_diveplan(diveplan, dp);
- return (dp);
-}
-
-struct gaschanges {
- int depth;
- int gasidx;
-};
-
-
-static struct gaschanges *analyze_gaslist(struct diveplan *diveplan, int *gaschangenr, int depth, int *asc_cylinder)
-{
- struct gasmix gas;
- int nr = 0;
- struct gaschanges *gaschanges = NULL;
- struct divedatapoint *dp = diveplan->dp;
- int best_depth = displayed_dive.cylinder[*asc_cylinder].depth.mm;
- while (dp) {
- if (dp->time == 0) {
- gas = dp->gasmix;
- if (dp->depth <= depth) {
- int i = 0;
- nr++;
- gaschanges = realloc(gaschanges, nr * sizeof(struct gaschanges));
- while (i < nr - 1) {
- if (dp->depth < gaschanges[i].depth) {
- memmove(gaschanges + i + 1, gaschanges + i, (nr - i - 1) * sizeof(struct gaschanges));
- break;
- }
- i++;
- }
- gaschanges[i].depth = dp->depth;
- gaschanges[i].gasidx = get_gasidx(&displayed_dive, &gas);
- assert(gaschanges[i].gasidx != -1);
- } else {
- /* is there a better mix to start deco? */
- if (dp->depth < best_depth) {
- best_depth = dp->depth;
- *asc_cylinder = get_gasidx(&displayed_dive, &gas);
- }
- }
- }
- dp = dp->next;
- }
- *gaschangenr = nr;
-#if DEBUG_PLAN & 16
- for (nr = 0; nr < *gaschangenr; nr++) {
- int idx = gaschanges[nr].gasidx;
- printf("gaschange nr %d: @ %5.2lfm gasidx %d (%s)\n", nr, gaschanges[nr].depth / 1000.0,
- idx, gasname(&displayed_dive.cylinder[idx].gasmix));
- }
-#endif
- return gaschanges;
-}
-
-/* sort all the stops into one ordered list */
-static int *sort_stops(int *dstops, int dnr, struct gaschanges *gstops, int gnr)
-{
- int i, gi, di;
- int total = dnr + gnr;
- int *stoplevels = malloc(total * sizeof(int));
-
- /* no gaschanges */
- if (gnr == 0) {
- memcpy(stoplevels, dstops, dnr * sizeof(int));
- return stoplevels;
- }
- i = total - 1;
- gi = gnr - 1;
- di = dnr - 1;
- while (i >= 0) {
- if (dstops[di] > gstops[gi].depth) {
- stoplevels[i] = dstops[di];
- di--;
- } else if (dstops[di] == gstops[gi].depth) {
- stoplevels[i] = dstops[di];
- di--;
- gi--;
- } else {
- stoplevels[i] = gstops[gi].depth;
- gi--;
- }
- i--;
- if (di < 0) {
- while (gi >= 0)
- stoplevels[i--] = gstops[gi--].depth;
- break;
- }
- if (gi < 0) {
- while (di >= 0)
- stoplevels[i--] = dstops[di--];
- break;
- }
- }
- while (i >= 0)
- stoplevels[i--] = 0;
-
-#if DEBUG_PLAN & 16
- int k;
- for (k = gnr + dnr - 1; k >= 0; k--) {
- printf("stoplevel[%d]: %5.2lfm\n", k, stoplevels[k] / 1000.0);
- if (stoplevels[k] == 0)
- break;
- }
-#endif
- return stoplevels;
-}
-
-static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, int error)
-{
- const unsigned int sz_buffer = 2000000;
- const unsigned int sz_temp = 100000;
- char *buffer = (char *)malloc(sz_buffer);
- char *temp = (char *)malloc(sz_temp);
- char *deco, *segmentsymbol;
- static char buf[1000];
- int len, lastdepth = 0, lasttime = 0, lastsetpoint = -1, newdepth = 0, lastprintdepth = 0, lastprintsetpoint = -1;
- struct gasmix lastprintgasmix = {{ -1 }, { -1 }};
- struct divedatapoint *dp = diveplan->dp;
- bool gaschange_after = !plan_verbatim;
- bool gaschange_before;
- bool lastentered = true;
- struct divedatapoint *nextdp = NULL;
-
- plan_verbatim = prefs.verbatim_plan;
- plan_display_runtime = prefs.display_runtime;
- plan_display_duration = prefs.display_duration;
- plan_display_transitions = prefs.display_transitions;
-
- if (prefs.deco_mode == VPMB) {
- deco = "VPM-B";
- } else {
- deco = "BUHLMANN";
- }
-
- snprintf(buf, sizeof(buf), translate("gettextFromC", "DISCLAIMER / WARNING: THIS IS A NEW IMPLEMENTATION OF THE %s "
- "ALGORITHM AND A DIVE PLANNER IMPLEMENTATION BASED ON THAT WHICH HAS "
- "RECEIVED ONLY A LIMITED AMOUNT OF TESTING. WE STRONGLY RECOMMEND NOT TO "
- "PLAN DIVES SIMPLY BASED ON THE RESULTS GIVEN HERE."), deco);
- disclaimer = buf;
-
- if (!dp) {
- free((void *)buffer);
- free((void *)temp);
- return;
- }
-
- if (error) {
- snprintf(temp, sz_temp, "%s",
- translate("gettextFromC", "Decompression calculation aborted due to excessive time"));
- snprintf(buffer, sz_buffer, "<span style='color: red;'>%s </span> %s<br>",
- translate("gettextFromC", "Warning:"), temp);
- dive->notes = strdup(buffer);
-
- free((void *)buffer);
- free((void *)temp);
- return;
- }
-
- len = show_disclaimer ? snprintf(buffer, sz_buffer, "<div><b>%s<b></div><br>", disclaimer) : 0;
- if (prefs.deco_mode == BUEHLMANN){
- snprintf(temp, sz_temp, translate("gettextFromC", "based on Bühlmann ZHL-16B with GFlow = %d and GFhigh = %d"),
- diveplan->gflow, diveplan->gfhigh);
- } else if (prefs.deco_mode == VPMB){
- if (prefs.conservatism_level == 0)
- snprintf(temp, sz_temp, "%s", translate("gettextFromC", "based on VPM-B at nominal conservatism"));
- else
- snprintf(temp, sz_temp, translate("gettextFromC", "based on VPM-B at +%d conservatism"), prefs.conservatism_level);
- } else if (prefs.deco_mode == RECREATIONAL){
- snprintf(temp, sz_temp, translate("gettextFromC", "recreational mode based on Bühlmann ZHL-16B with GFlow = %d and GFhigh = %d"),
- diveplan->gflow, diveplan->gfhigh);
- }
- len += snprintf(buffer + len, sz_buffer - len, "<div><b>%s</b><br>%s</div><br>",
- translate("gettextFromC", "Subsurface dive plan"), temp);
-
- if (!plan_verbatim) {
- len += snprintf(buffer + len, sz_buffer - len, "<div><table><thead><tr><th></th><th>%s</th>",
- translate("gettextFromC", "depth"));
- if (plan_display_duration)
- len += snprintf(buffer + len, sz_buffer - len, "<th style='padding-left: 10px;'>%s</th>",
- translate("gettextFromC", "duration"));
- if (plan_display_runtime)
- len += snprintf(buffer + len, sz_buffer - len, "<th style='padding-left: 10px;'>%s</th>",
- translate("gettextFromC", "runtime"));
- len += snprintf(buffer + len, sz_buffer - len,
- "<th style='padding-left: 10px; float: left;'>%s</th></tr></thead><tbody style='float: left;'>",
- translate("gettextFromC", "gas"));
- }
- do {
- struct gasmix gasmix, newgasmix = {};
- const char *depth_unit;
- double depthvalue;
- int decimals;
- bool isascent = (dp->depth < lastdepth);
-
- nextdp = dp->next;
- if (dp->time == 0)
- continue;
- gasmix = dp->gasmix;
- depthvalue = get_depth_units(dp->depth, &decimals, &depth_unit);
- /* analyze the dive points ahead */
- while (nextdp && nextdp->time == 0)
- nextdp = nextdp->next;
- if (nextdp)
- newgasmix = nextdp->gasmix;
- gaschange_after = (nextdp && (gasmix_distance(&gasmix, &newgasmix) || dp->setpoint != nextdp->setpoint));
- gaschange_before = (gasmix_distance(&lastprintgasmix, &gasmix) || lastprintsetpoint != dp->setpoint);
- /* do we want to skip this leg as it is devoid of anything useful? */
- if (!dp->entered &&
- nextdp &&
- dp->depth != lastdepth &&
- nextdp->depth != dp->depth &&
- !gaschange_before &&
- !gaschange_after)
- continue;
- if (dp->time - lasttime < 10 && !(gaschange_after && dp->next && dp->depth != dp->next->depth))
- continue;
-
- len = strlen(buffer);
- if (plan_verbatim) {
- /* When displaying a verbatim plan, we output a waypoint for every gas change.
- * Therefore, we do not need to test for difficult cases that mean we need to
- * print a segment just so we don't miss a gas change. This makes the logic
- * to determine whether or not to print a segment much simpler than with the
- * non-verbatim plan.
- */
- if (dp->depth != lastprintdepth) {
- if (plan_display_transitions || dp->entered || !dp->next || (gaschange_after && dp->next && dp->depth != nextdp->depth)) {
- if (dp->setpoint)
- snprintf(temp, sz_temp, translate("gettextFromC", "Transition to %.*f %s in %d:%02d min - runtime %d:%02u on %s (SP = %.1fbar)"),
- decimals, depthvalue, depth_unit,
- FRACTION(dp->time - lasttime, 60),
- FRACTION(dp->time, 60),
- gasname(&gasmix),
- (double) dp->setpoint / 1000.0);
-
- else
- snprintf(temp, sz_temp, translate("gettextFromC", "Transition to %.*f %s in %d:%02d min - runtime %d:%02u on %s"),
- decimals, depthvalue, depth_unit,
- FRACTION(dp->time - lasttime, 60),
- FRACTION(dp->time, 60),
- gasname(&gasmix));
-
- len += snprintf(buffer + len, sz_buffer - len, "%s<br>", temp);
- }
- newdepth = dp->depth;
- lasttime = dp->time;
- } else {
- if ((nextdp && dp->depth != nextdp->depth) || gaschange_after) {
- if (dp->setpoint)
- snprintf(temp, sz_temp, translate("gettextFromC", "Stay at %.*f %s for %d:%02d min - runtime %d:%02u on %s (SP = %.1fbar)"),
- decimals, depthvalue, depth_unit,
- FRACTION(dp->time - lasttime, 60),
- FRACTION(dp->time, 60),
- gasname(&gasmix),
- (double) dp->setpoint / 1000.0);
- else
- snprintf(temp, sz_temp, translate("gettextFromC", "Stay at %.*f %s for %d:%02d min - runtime %d:%02u on %s"),
- decimals, depthvalue, depth_unit,
- FRACTION(dp->time - lasttime, 60),
- FRACTION(dp->time, 60),
- gasname(&gasmix));
-
- len += snprintf(buffer + len, sz_buffer - len, "%s<br>", temp);
- newdepth = dp->depth;
- lasttime = dp->time;
- }
- }
- } else {
- /* When not displaying the verbatim dive plan, we typically ignore ascents between deco stops,
- * unless the display transitions option has been selected. We output a segment if any of the
- * following conditions are met.
- * 1) Display transitions is selected
- * 2) The segment was manually entered
- * 3) It is the last segment of the dive
- * 4) The segment is not an ascent, there was a gas change at the start of the segment and the next segment
- * is a change in depth (typical deco stop)
- * 5) There is a gas change at the end of the segment and the last segment was entered (first calculated
- * segment if it ends in a gas change)
- * 6) There is a gaschange after but no ascent. This should only occur when backgas breaks option is selected
- * 7) It is an ascent ending with a gas change, but is not followed by a stop. As case 5 already matches
- * the first calculated ascent if it ends with a gas change, this should only occur if a travel gas is
- * used for a calculated ascent, there is a subsequent gas change before the first deco stop, and zero
- * time has been allowed for a gas switch.
- */
- if (plan_display_transitions || dp->entered || !dp->next ||
- (nextdp && dp->depth != nextdp->depth) ||
- (!isascent && gaschange_before && nextdp && dp->depth != nextdp->depth) ||
- (gaschange_after && lastentered) || (gaschange_after && !isascent) ||
- (isascent && gaschange_after && nextdp && dp->depth != nextdp->depth )) {
- // Print a symbol to indicate whether segment is an ascent, descent, constant depth (user entered) or deco stop
- if (isascent)
- segmentsymbol = "&#10138;"; // up-right arrow for ascent
- else if (dp->depth > lastdepth)
- segmentsymbol = "&#10136;"; // down-right arrow for descent
- else if (dp->entered)
- segmentsymbol = "&#10137;"; // right arrow for entered entered segment at constant depth
- else
- segmentsymbol = "&#10134;"; // heavey minus sign for deco stop
-
- len += snprintf(buffer + len, sz_buffer - len, "<tr><td style='padding-left: 10px; float: right;'>%s</td>", segmentsymbol);
-
- snprintf(temp, sz_temp, translate("gettextFromC", "%3.0f%s"), depthvalue, depth_unit);
- len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; float: right;'>%s</td>", temp);
- if (plan_display_duration) {
- snprintf(temp, sz_temp, translate("gettextFromC", "%3dmin"), (dp->time - lasttime + 30) / 60);
- len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; float: right;'>%s</td>", temp);
- }
- if (plan_display_runtime) {
- snprintf(temp, sz_temp, translate("gettextFromC", "%3dmin"), (dp->time + 30) / 60);
- len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; float: right;'>%s</td>", temp);
- }
-
- /* Normally a gas change is displayed on the stopping segment, so only display a gas change at the end of
- * an ascent segment if it is not followed by a stop
- */
- if (isascent && gaschange_after && dp->next && nextdp && dp->depth != nextdp->depth) {
- if (dp->setpoint) {
- snprintf(temp, sz_temp, translate("gettextFromC", "(SP = %.1fbar)"), (double) nextdp->setpoint / 1000.0);
- len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; color: red; float: left;'><b>%s %s</b></td>", gasname(&newgasmix),
- temp);
- } else {
- len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; color: red; float: left;'><b>%s</b></td>", gasname(&newgasmix));
- }
- lastprintsetpoint = nextdp->setpoint;
- lastprintgasmix = newgasmix;
- gaschange_after = false;
- } else if (gaschange_before) {
- // If a new gas has been used for this segment, now is the time to show it
- if (dp->setpoint) {
- snprintf(temp, sz_temp, translate("gettextFromC", "(SP = %.1fbar)"), (double) dp->setpoint / 1000.0);
- len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; color: red; float: left;'><b>%s %s</b></td>", gasname(&gasmix),
- temp);
- } else {
- len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; color: red; float: left;'><b>%s</b></td>", gasname(&gasmix));
- }
- // Set variables so subsequent iterations can test against the last gas printed
- lastprintsetpoint = dp->setpoint;
- lastprintgasmix = gasmix;
- gaschange_after = false;
- } else {
- len += snprintf(buffer + len, sz_buffer - len, "<td>&nbsp;</td>");
- }
- len += snprintf(buffer + len, sz_buffer - len, "</tr>");
- newdepth = dp->depth;
- lasttime = dp->time;
- }
- }
- if (gaschange_after) {
- // gas switch at this waypoint
- if (plan_verbatim) {
- if (lastsetpoint >= 0) {
- if (nextdp && nextdp->setpoint)
- snprintf(temp, sz_temp, translate("gettextFromC", "Switch gas to %s (SP = %.1fbar)"), gasname(&newgasmix), (double) nextdp->setpoint / 1000.0);
- else
- snprintf(temp, sz_temp, translate("gettextFromC", "Switch gas to %s"), gasname(&newgasmix));
-
- len += snprintf(buffer + len, sz_buffer - len, "%s<br>", temp);
- }
- gaschange_after = false;
- gasmix = newgasmix;
- }
- }
- lastprintdepth = newdepth;
- lastdepth = dp->depth;
- lastsetpoint = dp->setpoint;
- lastentered = dp->entered;
- } while ((dp = nextdp) != NULL);
- if (!plan_verbatim)
- len += snprintf(buffer + len, sz_buffer - len, "</tbody></table></div>");
-
- dive->cns = 0;
- dive->maxcns = 0;
- update_cylinder_related_info(dive);
- snprintf(temp, sz_temp, "%s", translate("gettextFromC", "CNS"));
- len += snprintf(buffer + len, sz_buffer - len, "<div><br>%s: %i%%", temp, dive->cns);
- snprintf(temp, sz_temp, "%s", translate("gettextFromC", "OTU"));
- len += snprintf(buffer + len, sz_buffer - len, "<br>%s: %i</div>", temp, dive->otu);
-
- if (dive->dc.divemode == CCR)
- snprintf(temp, sz_temp, "%s", translate("gettextFromC", "Gas consumption (CCR legs excluded):"));
- else
- snprintf(temp, sz_temp, "%s", translate("gettextFromC", "Gas consumption:"));
- len += snprintf(buffer + len, sz_buffer - len, "<div><br>%s<br>", temp);
- for (int gasidx = 0; gasidx < MAX_CYLINDERS; gasidx++) {
- double volume, pressure, deco_volume, deco_pressure;
- const char *unit, *pressure_unit;
- char warning[1000] = "";
- cylinder_t *cyl = &dive->cylinder[gasidx];
- if (cylinder_none(cyl))
- break;
-
- volume = get_volume_units(cyl->gas_used.mliter, NULL, &unit);
- deco_volume = get_volume_units(cyl->deco_gas_used.mliter, NULL, &unit);
- if (cyl->type.size.mliter) {
- deco_pressure = get_pressure_units(1000.0 * cyl->deco_gas_used.mliter / cyl->type.size.mliter, &pressure_unit);
- pressure = get_pressure_units(1000.0 * cyl->gas_used.mliter / cyl->type.size.mliter, &pressure_unit);
- /* Warn if the plan uses more gas than is available in a cylinder
- * This only works if we have working pressure for the cylinder
- * 10bar is a made up number - but it seemed silly to pretend you could breathe cylinder down to 0 */
- if (cyl->end.mbar < 10000)
- snprintf(warning, sizeof(warning), " &mdash; <span style='color: red;'>%s </span> %s",
- translate("gettextFromC", "Warning:"),
- translate("gettextFromC", "this is more gas than available in the specified cylinder!"));
- else
- if ((float) cyl->end.mbar * cyl->type.size.mliter / 1000.0 < (float) cyl->deco_gas_used.mliter)
- snprintf(warning, sizeof(warning), " &mdash; <span style='color: red;'>%s </span> %s",
- translate("gettextFromC", "Warning:"),
- translate("gettextFromC", "not enough reserve for gas sharing on ascent!"));
-
- snprintf(temp, sz_temp, translate("gettextFromC", "%.0f%s/%.0f%s of %s (%.0f%s/%.0f%s in planned ascent)"), volume, unit, pressure, pressure_unit, gasname(&cyl->gasmix), deco_volume, unit, deco_pressure, pressure_unit);
- } else {
- snprintf(temp, sz_temp, translate("gettextFromC", "%.0f%s (%.0f%s during planned ascent) of %s"), volume, unit, deco_volume, unit, gasname(&cyl->gasmix));
- }
- len += snprintf(buffer + len, sz_buffer - len, "%s%s<br>", temp, warning);
- }
- dp = diveplan->dp;
- if (dive->dc.divemode != CCR) {
- while (dp) {
- if (dp->time != 0) {
- struct gas_pressures pressures;
- fill_pressures(&pressures, depth_to_atm(dp->depth, dive), &dp->gasmix, 0.0, dive->dc.divemode);
-
- if (pressures.o2 > (dp->entered ? prefs.bottompo2 : prefs.decopo2) / 1000.0) {
- const char *depth_unit;
- int decimals;
- double depth_value = get_depth_units(dp->depth, &decimals, &depth_unit);
- len = strlen(buffer);
- snprintf(temp, sz_temp,
- translate("gettextFromC", "high pO₂ value %.2f at %d:%02u with gas %s at depth %.*f %s"),
- pressures.o2, FRACTION(dp->time, 60), gasname(&dp->gasmix), decimals, depth_value, depth_unit);
- len += snprintf(buffer + len, sz_buffer - len, "<span style='color: red;'>%s </span> %s<br>",
- translate("gettextFromC", "Warning:"), temp);
- } else if (pressures.o2 < 0.16) {
- const char *depth_unit;
- int decimals;
- double depth_value = get_depth_units(dp->depth, &decimals, &depth_unit);
- len = strlen(buffer);
- snprintf(temp, sz_temp,
- translate("gettextFromC", "low pO₂ value %.2f at %d:%02u with gas %s at depth %.*f %s"),
- pressures.o2, FRACTION(dp->time, 60), gasname(&dp->gasmix), decimals, depth_value, depth_unit);
- len += snprintf(buffer + len, sz_buffer - len, "<span style='color: red;'>%s </span> %s<br>",
- translate("gettextFromC", "Warning:"), temp);
-
- }
- }
- dp = dp->next;
- }
- }
- snprintf(buffer + len, sz_buffer - len, "</div>");
- dive->notes = strdup(buffer);
-
- free((void *)buffer);
- free((void *)temp);
-}
-
-int ascent_velocity(int depth, int avg_depth, int bottom_time)
-{
- (void) bottom_time;
- /* We need to make this configurable */
-
- /* As an example (and possibly reasonable default) this is the Tech 1 provedure according
- * to http://www.globalunderwaterexplorers.org/files/Standards_and_Procedures/SOP_Manual_Ver2.0.2.pdf */
-
- if (depth * 4 > avg_depth * 3) {
- return prefs.ascrate75;
- } else {
- if (depth * 2 > avg_depth) {
- return prefs.ascrate50;
- } else {
- if (depth > 6000)
- return prefs.ascratestops;
- else
- return prefs.ascratelast6m;
- }
- }
-}
-
-void track_ascent_gas(int depth, cylinder_t *cylinder, int avg_depth, int bottom_time, bool safety_stop)
-{
- while (depth > 0) {
- int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP;
- if (deltad > depth)
- deltad = depth;
- update_cylinder_pressure(&displayed_dive, depth, depth - deltad, TIMESTEP, prefs.decosac, cylinder, true);
- if (depth <= 5000 && depth >= (5000 - deltad) && safety_stop) {
- update_cylinder_pressure(&displayed_dive, 5000, 5000, 180, prefs.decosac, cylinder, true);
- safety_stop = false;
- }
- depth -= deltad;
- }
-}
-
-// Determine whether ascending to the next stop will break the ceiling. Return true if the ascent is ok, false if it isn't.
-bool trial_ascent(int trial_depth, int stoplevel, int avg_depth, int bottom_time, struct gasmix *gasmix, int po2, double surface_pressure)
-{
-
- bool clear_to_ascend = true;
- char *trial_cache = NULL;
-
- // For consistency with other VPM-B implementations, we should not start the ascent while the ceiling is
- // deeper than the next stop (thus the offgasing during the ascent is ignored).
- // However, we still need to make sure we don't break the ceiling due to on-gassing during ascent.
- if (prefs.deco_mode == VPMB && (deco_allowed_depth(tissue_tolerance_calc(&displayed_dive,
- depth_to_bar(stoplevel, &displayed_dive)),
- surface_pressure, &displayed_dive, 1) > stoplevel))
- return false;
-
- cache_deco_state(&trial_cache);
- while (trial_depth > stoplevel) {
- int deltad = ascent_velocity(trial_depth, avg_depth, bottom_time) * TIMESTEP;
- if (deltad > trial_depth) /* don't test against depth above surface */
- deltad = trial_depth;
- add_segment(depth_to_bar(trial_depth, &displayed_dive),
- gasmix,
- TIMESTEP, po2, &displayed_dive, prefs.decosac);
- if (deco_allowed_depth(tissue_tolerance_calc(&displayed_dive, depth_to_bar(trial_depth, &displayed_dive)),
- surface_pressure, &displayed_dive, 1) > trial_depth - deltad) {
- /* We should have stopped */
- clear_to_ascend = false;
- break;
- }
- trial_depth -= deltad;
- }
- restore_deco_state(trial_cache);
- free(trial_cache);
- return clear_to_ascend;
-}
-
-/* Determine if there is enough gas for the dive. Return true if there is enough.
- * Also return true if this cannot be calculated because the cylinder doesn't have
- * size or a starting pressure.
- */
-bool enough_gas(int current_cylinder)
-{
- cylinder_t *cyl;
- cyl = &displayed_dive.cylinder[current_cylinder];
-
- if (!cyl->start.mbar)
- return true;
- if (cyl->type.size.mliter)
- return (float) (cyl->end.mbar - prefs.reserve_gas) * cyl->type.size.mliter / 1000.0 > (float) cyl->deco_gas_used.mliter;
- else
- return true;
-}
-
-// Work out the stops. Return value is if there were any mandatory stops.
-
-bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer)
-{
- int bottom_depth;
- int bottom_gi;
- int bottom_stopidx;
- bool is_final_plan = true;
- int deco_time;
- int previous_deco_time;
- char *bottom_cache = NULL;
- struct sample *sample;
- int po2;
- int transitiontime, gi;
- int current_cylinder;
- int stopidx;
- int depth;
- struct gaschanges *gaschanges = NULL;
- int gaschangenr;
- int *decostoplevels;
- int decostoplevelcount;
- int *stoplevels = NULL;
- bool stopping = false;
- bool pendinggaschange = false;
- int clock, previous_point_time;
- int avg_depth, max_depth, bottom_time = 0;
- int last_ascend_rate;
- int best_first_ascend_cylinder;
- struct gasmix gas, bottom_gas;
- int o2time = 0;
- int breaktime = -1;
- int breakcylinder = 0;
- int error = 0;
- bool decodive = false;
-
- set_gf(diveplan->gflow, diveplan->gfhigh, prefs.gf_low_at_maxdepth);
- if (!diveplan->surface_pressure)
- diveplan->surface_pressure = SURFACE_PRESSURE;
- displayed_dive.surface_pressure.mbar = diveplan->surface_pressure;
- clear_deco(displayed_dive.surface_pressure.mbar / 1000.0);
- max_bottom_ceiling_pressure.mbar = first_ceiling_pressure.mbar = 0;
- create_dive_from_plan(diveplan, is_planner);
-
- // Do we want deco stop array in metres or feet?
- if (prefs.units.length == METERS ) {
- decostoplevels = decostoplevels_metric;
- decostoplevelcount = sizeof(decostoplevels_metric) / sizeof(int);
- } else {
- decostoplevels = decostoplevels_imperial;
- decostoplevelcount = sizeof(decostoplevels_imperial) / sizeof(int);
- }
-
- /* If the user has selected last stop to be at 6m/20', we need to get rid of the 3m/10' stop.
- * Otherwise reinstate the last stop 3m/10' stop.
- */
- if (prefs.last_stop)
- *(decostoplevels + 1) = 0;
- else
- *(decostoplevels + 1) = M_OR_FT(3,10);
-
- /* Let's start at the last 'sample', i.e. the last manually entered waypoint. */
- sample = &displayed_dive.dc.sample[displayed_dive.dc.samples - 1];
-
- get_gas_at_time(&displayed_dive, &displayed_dive.dc, sample->time, &gas);
-
- po2 = sample->setpoint.mbar;
- if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) {
- report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas));
- current_cylinder = 0;
- }
- depth = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].depth.mm;
- average_max_depth(diveplan, &avg_depth, &max_depth);
- last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time);
-
- /* if all we wanted was the dive just get us back to the surface */
- if (!is_planner) {
- transitiontime = depth / 75; /* this still needs to be made configurable */
- plan_add_segment(diveplan, transitiontime, 0, gas, po2, false);
- create_dive_from_plan(diveplan, is_planner);
- return(false);
- }
-
-#if DEBUG_PLAN & 4
- printf("gas %s\n", gasname(&gas));
- printf("depth %5.2lfm \n", depth / 1000.0);
-#endif
-
- best_first_ascend_cylinder = current_cylinder;
- /* Find the gases available for deco */
-
- if (po2) { // Don't change gas in CCR mode
- gaschanges = NULL;
- gaschangenr = 0;
- } else {
- gaschanges = analyze_gaslist(diveplan, &gaschangenr, depth, &best_first_ascend_cylinder);
- }
- /* Find the first potential decostopdepth above current depth */
- for (stopidx = 0; stopidx < decostoplevelcount; stopidx++)
- if (*(decostoplevels + stopidx) >= depth)
- break;
- if (stopidx > 0)
- stopidx--;
- /* Stoplevels are either depths of gas changes or potential deco stop depths. */
- stoplevels = sort_stops(decostoplevels, stopidx + 1, gaschanges, gaschangenr);
- stopidx += gaschangenr;
-
- /* Keep time during the ascend */
- bottom_time = clock = previous_point_time = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].time.seconds;
- gi = gaschangenr - 1;
-
- /* Set tissue tolerance and initial vpmb gradient at start of ascent phase */
- tissue_at_end(&displayed_dive, cached_datap);
- nuclear_regeneration(clock);
- vpmb_start_gradient();
-
- if(prefs.deco_mode == RECREATIONAL) {
- bool safety_stop = prefs.safetystop && max_depth >= 10000;
- track_ascent_gas(depth, &displayed_dive.cylinder[current_cylinder], avg_depth, bottom_time, safety_stop);
- // How long can we stay at the current depth and still directly ascent to the surface?
- do {
- add_segment(depth_to_bar(depth, &displayed_dive),
- &displayed_dive.cylinder[current_cylinder].gasmix,
- DECOTIMESTEP, po2, &displayed_dive, prefs.bottomsac);
- update_cylinder_pressure(&displayed_dive, depth, depth, DECOTIMESTEP, prefs.bottomsac, &displayed_dive.cylinder[current_cylinder], false);
- clock += DECOTIMESTEP;
- } while (trial_ascent(depth, 0, avg_depth, bottom_time, &displayed_dive.cylinder[current_cylinder].gasmix,
- po2, diveplan->surface_pressure / 1000.0) &&
- enough_gas(current_cylinder));
-
- // We did stay one DECOTIMESTEP too many.
- // In the best of all worlds, we would roll back also the last add_segment in terms of caching deco state, but
- // let's ignore that since for the eventual ascent in recreational mode, nobody looks at the ceiling anymore,
- // so we don't really have to compute the deco state.
- update_cylinder_pressure(&displayed_dive, depth, depth, -DECOTIMESTEP, prefs.bottomsac, &displayed_dive.cylinder[current_cylinder], false);
- clock -= DECOTIMESTEP;
- plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, true);
- previous_point_time = clock;
- do {
- /* Ascend to surface */
- int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP;
- if (ascent_velocity(depth, avg_depth, bottom_time) != last_ascend_rate) {
- plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false);
- previous_point_time = clock;
- last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time);
- }
- if (depth - deltad < 0)
- deltad = depth;
-
- clock += TIMESTEP;
- depth -= deltad;
- if (depth <= 5000 && depth >= (5000 - deltad) && safety_stop) {
- plan_add_segment(diveplan, clock - previous_point_time, 5000, gas, po2, false);
- previous_point_time = clock;
- clock += 180;
- plan_add_segment(diveplan, clock - previous_point_time, 5000, gas, po2, false);
- previous_point_time = clock;
- safety_stop = false;
- }
- } while (depth > 0);
- plan_add_segment(diveplan, clock - previous_point_time, 0, gas, po2, false);
- create_dive_from_plan(diveplan, is_planner);
- add_plan_to_notes(diveplan, &displayed_dive, show_disclaimer, error);
- fixup_dc_duration(&displayed_dive.dc);
-
- free(stoplevels);
- free(gaschanges);
- return(false);
- }
-
- if (best_first_ascend_cylinder != current_cylinder) {
- current_cylinder = best_first_ascend_cylinder;
- gas = displayed_dive.cylinder[current_cylinder].gasmix;
-
-#if DEBUG_PLAN & 16
- printf("switch to gas %d (%d/%d) @ %5.2lfm\n", best_first_ascend_cylinder,
- (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[best_first_ascend_cylinder].depth / 1000.0);
-#endif
- }
-
- // VPM-B or Buehlmann Deco
- tissue_at_end(&displayed_dive, cached_datap);
- previous_deco_time = 100000000;
- deco_time = 10000000;
- cache_deco_state(&bottom_cache); // Lets us make several iterations
- bottom_depth = depth;
- bottom_gi = gi;
- bottom_gas = gas;
- bottom_stopidx = stopidx;
-
- //CVA
- do {
- is_final_plan = (prefs.deco_mode == BUEHLMANN) || (previous_deco_time - deco_time < 10); // CVA time converges
- if (deco_time != 10000000)
- vpmb_next_gradient(deco_time, diveplan->surface_pressure / 1000.0);
-
- previous_deco_time = deco_time;
- restore_deco_state(bottom_cache);
-
- depth = bottom_depth;
- gi = bottom_gi;
- clock = previous_point_time = bottom_time;
- gas = bottom_gas;
- stopping = false;
- decodive = false;
- stopidx = bottom_stopidx;
- breaktime = -1;
- breakcylinder = 0;
- o2time = 0;
- first_ceiling_pressure.mbar = depth_to_mbar(deco_allowed_depth(tissue_tolerance_calc(&displayed_dive,
- depth_to_bar(depth, &displayed_dive)),
- diveplan->surface_pressure / 1000.0,
- &displayed_dive,
- 1),
- &displayed_dive);
- if (max_bottom_ceiling_pressure.mbar > first_ceiling_pressure.mbar)
- first_ceiling_pressure.mbar = max_bottom_ceiling_pressure.mbar;
-
- last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time);
- if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) {
- report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas));
- current_cylinder = 0;
- }
-
- while (1) {
- /* We will break out when we hit the surface */
- do {
- /* Ascend to next stop depth */
- int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP;
- if (ascent_velocity(depth, avg_depth, bottom_time) != last_ascend_rate) {
- if (is_final_plan)
- plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false);
- previous_point_time = clock;
- stopping = false;
- last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time);
- }
- if (depth - deltad < stoplevels[stopidx])
- deltad = depth - stoplevels[stopidx];
-
- add_segment(depth_to_bar(depth, &displayed_dive),
- &displayed_dive.cylinder[current_cylinder].gasmix,
- TIMESTEP, po2, &displayed_dive, prefs.decosac);
- clock += TIMESTEP;
- depth -= deltad;
- } while (depth > 0 && depth > stoplevels[stopidx]);
-
- if (depth <= 0)
- break; /* We are at the surface */
-
- if (gi >= 0 && stoplevels[stopidx] <= gaschanges[gi].depth) {
- /* We have reached a gas change.
- * Record this in the dive plan */
- if (is_final_plan)
- plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false);
- previous_point_time = clock;
- stopping = true;
-
- /* Check we need to change cylinder.
- * We might not if the cylinder was chosen by the user
- * or user has selected only to switch only at required stops.
- * If current gas is hypoxic, we want to switch asap */
-
- if (current_cylinder != gaschanges[gi].gasidx) {
- if (!prefs.switch_at_req_stop ||
- !trial_ascent(depth, stoplevels[stopidx - 1], avg_depth, bottom_time,
- &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0) || get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) < 160) {
- current_cylinder = gaschanges[gi].gasidx;
- gas = displayed_dive.cylinder[current_cylinder].gasmix;
-#if DEBUG_PLAN & 16
- printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi].gasidx,
- (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi].depth / 1000.0);
-#endif
- /* Stop for the minimum duration to switch gas */
- add_segment(depth_to_bar(depth, &displayed_dive),
- &displayed_dive.cylinder[current_cylinder].gasmix,
- prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac);
- clock += prefs.min_switch_duration;
- if (prefs.doo2breaks && get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000)
- o2time += prefs.min_switch_duration;
- } else {
- /* The user has selected the option to switch gas only at required stops.
- * Remember that we are waiting to switch gas
- */
- pendinggaschange = true;
- }
- }
- gi--;
- }
- --stopidx;
-
- /* Save the current state and try to ascend to the next stopdepth */
- while (1) {
- /* Check if ascending to next stop is clear, go back and wait if we hit the ceiling on the way */
- if (trial_ascent(depth, stoplevels[stopidx], avg_depth, bottom_time,
- &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0))
- break; /* We did not hit the ceiling */
-
- /* Add a minute of deco time and then try again */
- decodive = true;
- if (!stopping) {
- /* The last segment was an ascend segment.
- * Add a waypoint for start of this deco stop */
- if (is_final_plan)
- plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false);
- previous_point_time = clock;
- stopping = true;
- }
-
- /* Are we waiting to switch gas?
- * Occurs when the user has selected the option to switch only at required stops
- */
- if (pendinggaschange) {
- current_cylinder = gaschanges[gi + 1].gasidx;
- gas = displayed_dive.cylinder[current_cylinder].gasmix;
-#if DEBUG_PLAN & 16
- printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi + 1].gasidx,
- (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi + 1].depth / 1000.0);
-#endif
- /* Stop for the minimum duration to switch gas */
- add_segment(depth_to_bar(depth, &displayed_dive),
- &displayed_dive.cylinder[current_cylinder].gasmix,
- prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac);
- clock += prefs.min_switch_duration;
- if (prefs.doo2breaks && get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000)
- o2time += prefs.min_switch_duration;
- pendinggaschange = false;
- }
-
- /* Deco stop should end when runtime is at a whole minute */
- int this_decotimestep;
- this_decotimestep = DECOTIMESTEP - clock % DECOTIMESTEP;
-
- add_segment(depth_to_bar(depth, &displayed_dive),
- &displayed_dive.cylinder[current_cylinder].gasmix,
- this_decotimestep, po2, &displayed_dive, prefs.decosac);
- clock += this_decotimestep;
- /* Finish infinite deco */
- if(clock >= 48 * 3600 && depth >= 6000) {
- error = LONGDECO;
- break;
- }
- if (prefs.doo2breaks) {
- /* The backgas breaks option limits time on oxygen to 12 minutes, followed by 6 minutes on
- * backgas (first defined gas). This could be customized if there were demand.
- */
- if (get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) {
- o2time += DECOTIMESTEP;
- if (o2time >= 12 * 60) {
- breaktime = 0;
- breakcylinder = current_cylinder;
- if (is_final_plan)
- plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false);
- previous_point_time = clock;
- current_cylinder = 0;
- gas = displayed_dive.cylinder[current_cylinder].gasmix;
- }
- } else {
- if (breaktime >= 0) {
- breaktime += DECOTIMESTEP;
- if (breaktime >= 6 * 60) {
- o2time = 0;
- if (is_final_plan)
- plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false);
- previous_point_time = clock;
- current_cylinder = breakcylinder;
- gas = displayed_dive.cylinder[current_cylinder].gasmix;
- breaktime = -1;
- }
- }
- }
- }
- }
- if (stopping) {
- /* Next we will ascend again. Add a waypoint if we have spend deco time */
- if (is_final_plan)
- plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false);
- previous_point_time = clock;
- stopping = false;
- }
- }
-
- deco_time = clock - bottom_time;
- } while (!is_final_plan);
-
- plan_add_segment(diveplan, clock - previous_point_time, 0, gas, po2, false);
- create_dive_from_plan(diveplan, is_planner);
- add_plan_to_notes(diveplan, &displayed_dive, show_disclaimer, error);
- fixup_dc_duration(&displayed_dive.dc);
-
- free(stoplevels);
- free(gaschanges);
- free(bottom_cache);
- return decodive;
-}
-
-/*
- * Get a value in tenths (so "10.2" == 102, "9" = 90)
- *
- * Return negative for errors.
- */
-static int get_tenths(const char *begin, const char **endp)
-{
- char *end;
- int value = strtol(begin, &end, 10);
-
- if (begin == end)
- return -1;
- value *= 10;
-
- /* Fraction? We only look at the first digit */
- if (*end == '.') {
- end++;
- if (!isdigit(*end))
- return -1;
- value += *end - '0';
- do {
- end++;
- } while (isdigit(*end));
- }
- *endp = end;
- return value;
-}
-
-static int get_permille(const char *begin, const char **end)
-{
- int value = get_tenths(begin, end);
- if (value >= 0) {
- /* Allow a percentage sign */
- if (**end == '%')
- ++*end;
- }
- return value;
-}
-
-int validate_gas(const char *text, struct gasmix *gas)
-{
- int o2, he;
-
- if (!text)
- return 0;
-
- while (isspace(*text))
- text++;
-
- if (!*text)
- return 0;
-
- if (!strcasecmp(text, translate("gettextFromC", "air"))) {
- o2 = O2_IN_AIR;
- he = 0;
- text += strlen(translate("gettextFromC", "air"));
- } else if (!strcasecmp(text, translate("gettextFromC", "oxygen"))) {
- o2 = 1000;
- he = 0;
- text += strlen(translate("gettextFromC", "oxygen"));
- } else if (!strncasecmp(text, translate("gettextFromC", "ean"), 3)) {
- o2 = get_permille(text + 3, &text);
- he = 0;
- } else {
- o2 = get_permille(text, &text);
- he = 0;
- if (*text == '/')
- he = get_permille(text + 1, &text);
- }
-
- /* We don't want any extra crud */
- while (isspace(*text))
- text++;
- if (*text)
- return 0;
-
- /* Validate the gas mix */
- if (*text || o2 < 1 || o2 > 1000 || he < 0 || o2 + he > 1000)
- return 0;
-
- /* Let it rip */
- gas->o2.permille = o2;
- gas->he.permille = he;
- return 1;
-}
-
-int validate_po2(const char *text, int *mbar_po2)
-{
- int po2;
-
- if (!text)
- return 0;
-
- po2 = get_tenths(text, &text);
- if (po2 < 0)
- return 0;
-
- while (isspace(*text))
- text++;
-
- while (isspace(*text))
- text++;
- if (*text)
- return 0;
-
- *mbar_po2 = po2 * 100;
- return 1;
-}