diff options
Diffstat (limited to 'subsurface-core/planner.c')
-rw-r--r-- | subsurface-core/planner.c | 1471 |
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 = "➚"; // 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; -} |