diff options
Diffstat (limited to 'core/planner.c')
-rw-r--r-- | core/planner.c | 1471 |
1 files changed, 1471 insertions, 0 deletions
diff --git a/core/planner.c b/core/planner.c new file mode 100644 index 000000000..705aad1cb --- /dev/null +++ b/core/planner.c @@ -0,0 +1,1471 @@ +/* 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 = "➚"; // up-right arrow for ascent + else if (dp->depth > lastdepth) + segmentsymbol = "➘"; // down-right arrow for descent + else if (dp->entered) + segmentsymbol = "➙"; // right arrow for entered entered segment at constant depth + else + segmentsymbol = "➖"; // 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> </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), " — <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), " — <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; +} |