void DiveEventItem::setupToolTipString() { // we display the event on screen - so translate QString name = gettextFromC::instance()->tr(internalEvent->name); int value = internalEvent->value; int type = internalEvent->type; if (value) { if (type == SAMPLE_EVENT_GASCHANGE || type == SAMPLE_EVENT_GASCHANGE2) { struct gasmix *g = get_gasmix_from_event(internalEvent); name += ": "; name += gasname(g); } else if (type == SAMPLE_EVENT_PO2 && name == "SP change") { name += QString(":%1").arg((double)value / 1000); } else { name += QString(":%1").arg(value); } } else if (type == SAMPLE_EVENT_PO2 && name == "SP change") { // this is a bad idea - we are abusing an existing event type that is supposed to // warn of high or low PO2 and are turning it into a set point change event name += "\n" + tr("Bailing out to OC"); } else { name += internalEvent->flags == SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") : internalEvent->flags == SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : ""; } // qDebug() << name; setToolTip(name); }
void DiveEventItem::setupToolTipString() { // we display the event on screen - so translate QString name = gettextFromC::instance()->tr(internalEvent->name); int value = internalEvent->value; int type = internalEvent->type; if (value) { if (event_is_gaschange(internalEvent)) { name += ": "; name += gasname(&internalEvent->gas.mix); /* Do we have an explicit cylinder index? Show it. */ if (internalEvent->gas.index >= 0) name += QString(" (cyl %1)").arg(internalEvent->gas.index+1); } else if (type == SAMPLE_EVENT_PO2 && name == "SP change") { name += QString(":%1").arg((double)value / 1000); } else { name += QString(":%1").arg(value); } } else if (type == SAMPLE_EVENT_PO2 && name == "SP change") { // this is a bad idea - we are abusing an existing event type that is supposed to // warn of high or low pO₂ and are turning it into a set point change event name += "\n" + tr("Manual switch to OC"); } else { name += internalEvent->flags == SAMPLE_FLAGS_BEGIN ? tr(" begin", "Starts with space!") : internalEvent->flags == SAMPLE_FLAGS_END ? tr(" end", "Starts with space!") : ""; } // qDebug() << name; setToolTip(name); }
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; }
void selectedDivesGasUsed(QVector<QPair<QString, int> > &gasUsedOrdered) { int i, j; struct dive *d; QMap<QString, int> gasUsed; for_each_dive (i, d) { if (!d->selected) continue; volume_t diveGases[MAX_CYLINDERS] = {}; get_gas_used(d, diveGases); for (j = 0; j < MAX_CYLINDERS; j++) if (diveGases[j].mliter) { QString gasName = gasname(&d->cylinder[j].gasmix); gasUsed[gasName] += diveGases[j].mliter; } } Q_FOREACH(const QString& gas, gasUsed.keys()) { gasUsedOrdered.append(qMakePair(gas, gasUsed[gas])); } qSort(gasUsedOrdered.begin(), gasUsedOrdered.end(), lessThan); }
void TankItem::createBar(qreal x, qreal w, struct gasmix *gas) { // pick the right gradient, size, position and text QGraphicsRectItem *rect = new QGraphicsRectItem(x, 0, w, height, this); if (gasmix_is_air(gas)) rect->setBrush(air); else if (gas->he.permille) rect->setBrush(trimix); else if (gas->o2.permille == 1000) rect->setBrush(oxygen); else rect->setBrush(nitrox); rect->setPen(QPen(QBrush(), 0.0)); // get rid of the thick line around the rectangle rects.push_back(rect); DiveTextItem *label = new DiveTextItem(rect); label->setText(gasname(gas)); label->setBrush(Qt::black); label->setPos(x + 1, 0); label->setAlignment(Qt::AlignBottom | Qt::AlignRight); label->setZValue(101); }
/* returns the tissue tolerance at the end of this (partial) dive */ double tissue_at_end(struct dive *dive, char **cached_datap) { struct divecomputer *dc; struct sample *sample, *psample; int i, t0, t1, gasidx, lastdepth; double tissue_tolerance; struct gasmix gas; if (!dive) return 0.0; if (*cached_datap) { tissue_tolerance = restore_deco_state(*cached_datap); } else { tissue_tolerance = init_decompression(dive); cache_deco_state(tissue_tolerance, cached_datap); } dc = &dive->dc; if (!dc->samples) return tissue_tolerance; psample = sample = dc->sample; lastdepth = t0 = 0; /* we always start with gas 0 (unless an event tells us otherwise) */ gas = dive->cylinder[0].gasmix; for (i = 0; i < dc->samples; i++, sample++) { t1 = sample->time.seconds; get_gas_from_events(&dive->dc, t0, &gas); if ((gasidx = get_gasidx(dive, &gas)) == -1) { report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas)); gasidx = 0; } if (i > 0) lastdepth = psample->depth.mm; tissue_tolerance = interpolate_transition(dive, t0, t1, lastdepth, sample->depth.mm, &dive->cylinder[gasidx].gasmix, sample->po2.mbar); psample = sample; t0 = t1; } return tissue_tolerance; }
/* for the O2/He readings just create a list of them */ char *get_gaslist(struct dive *dive) { int idx, offset = 0; static char buf[MAXBUF]; buf[0] = '\0'; for (idx = 0; idx < MAX_CYLINDERS; idx++) { cylinder_t *cyl; if (!is_cylinder_used(dive, idx)) continue; cyl = &dive->cylinder[idx]; if (offset > 0) { strncpy(buf + offset, "\n", MAXBUF - offset); offset = strlen(buf); } strncpy(buf + offset, gasname(&cyl->gasmix), MAXBUF - offset); offset = strlen(buf); } if (*buf == '\0') strncpy(buf, translate("gettextFromC", "air"), MAXBUF); buf[MAXBUF - 1] = '\0'; return buf; }
void plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer) { struct sample *sample; int po2; int transitiontime, gi; int current_cylinder; unsigned int stopidx; int depth; double tissue_tolerance = 0.0; struct gaschanges *gaschanges = NULL; int gaschangenr; int *stoplevels = NULL; char *trial_cache = NULL; bool stopping = false; bool clear_to_ascend; int clock, previous_point_time; int avg_depth, bottom_time = 0; int last_ascend_rate; int best_first_ascend_cylinder; struct gasmix gas; int o2time = 0; int breaktime = -1; int breakcylinder; set_gf(diveplan->gflow, diveplan->gfhigh, prefs.gf_low_at_maxdepth); if (!diveplan->surface_pressure) diveplan->surface_pressure = SURFACE_PRESSURE; create_dive_from_plan(diveplan, is_planner); /* 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 = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].po2.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; avg_depth = average_depth(diveplan); last_ascend_rate = ascend_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; } tissue_tolerance = tissue_at_end(&displayed_dive, cached_datap); #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 */ gaschanges = analyze_gaslist(diveplan, &gaschangenr, depth, &best_first_ascend_cylinder); /* Find the first potential decostopdepth above current depth */ for (stopidx = 0; stopidx < sizeof(decostoplevels) / sizeof(int); 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; if (best_first_ascend_cylinder != current_cylinder) { stopping = true; 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 } while (1) { /* We will break out when we hit the surface */ do { /* Ascend to next stop depth */ int deltad = ascend_velocity(depth, avg_depth, bottom_time) * TIMESTEP; if (ascend_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; stopping = false; last_ascend_rate = ascend_velocity(depth, avg_depth, bottom_time); } if (depth - deltad < stoplevels[stopidx]) deltad = depth - stoplevels[stopidx]; tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, TIMESTEP, po2, &displayed_dive); clock += TIMESTEP; depth -= deltad; } while (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 */ plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; stopping = true; 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 gi--; } --stopidx; /* Save the current state and try to ascend to the next stopdepth */ int trial_depth = depth; cache_deco_state(tissue_tolerance, &trial_cache); while (1) { /* Check if ascending to next stop is clear, go back and wait if we hit the ceiling on the way */ clear_to_ascend = true; while (trial_depth > stoplevels[stopidx]) { int deltad = ascend_velocity(trial_depth, avg_depth, bottom_time) * TIMESTEP; if (deltad > trial_depth) /* don't test against depth above surface */ deltad = trial_depth; tissue_tolerance = add_segment(depth_to_mbar(trial_depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, TIMESTEP, po2, &displayed_dive); if (deco_allowed_depth(tissue_tolerance, diveplan->surface_pressure / 1000.0, &displayed_dive, 1) > trial_depth - deltad) { /* We should have stopped */ clear_to_ascend = false; break; } trial_depth -= deltad; } restore_deco_state(trial_cache); if (clear_to_ascend) break; /* We did not hit the ceiling */ /* Add a minute of deco time and then try again */ if (!stopping) { /* The last segment was an ascend segment. * Add a waypoint for start of this deco stop */ plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; stopping = true; } tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, DECOTIMESTEP, po2, &displayed_dive); cache_deco_state(tissue_tolerance, &trial_cache); clock += DECOTIMESTEP; if (prefs.doo2breaks) { if (get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) { o2time += DECOTIMESTEP; if (o2time >= 12 * 60) { breaktime = 0; breakcylinder = current_cylinder; 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; 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; } } } } trial_depth = depth; } if (stopping) { /* Next we will ascend again. Add a waypoint if we have spend deco time */ plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; stopping = false; } } /* We made it to the surface */ 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); free(stoplevels); free(gaschanges); }
static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer) { char buffer[20000], temp[1000]; int len, lastdepth = 0, lasttime = 0; struct divedatapoint *dp = diveplan->dp; bool gaschange = !plan_verbatim; struct divedatapoint *nextdp = NULL; disclaimer = translate("gettextFromC", "DISCLAIMER / WARNING: THIS IS A NEW IMPLEMENTATION OF THE BUHLMANN " "ALGORITHM AND A DIVE PLANNER IMPLEMENTION 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."); if (!dp) return; len = show_disclaimer ? snprintf(buffer, sizeof(buffer), "<div><b>%s<b></div><br>", disclaimer) : 0; snprintf(temp, sizeof(temp), translate("gettextFromC", "based on GFlow = %d and GFhigh = %d"), diveplan->gflow, diveplan->gfhigh); len += snprintf(buffer + len, sizeof(buffer) - len, "<div><b>%s</b><br>%s</div><br>", translate("gettextFromC", "Subsurface dive plan"), temp); if (!plan_verbatim) { len += snprintf(buffer + len, sizeof(buffer) - len, "<div><table><thead><tr><th>%s</th>", translate("gettextFromC", "depth")); if (plan_display_runtime) len += snprintf(buffer + len, sizeof(buffer) - len, "<th style='padding-left: 10px;'>%s</th>", translate("gettextFromC", "runtime")); if (plan_display_duration) len += snprintf(buffer + len, sizeof(buffer) - len, "<th style='padding-left: 10px;'>%s</th>", translate("gettextFromC", "duration")); len += snprintf(buffer + len, sizeof(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; if (dp->time == 0) continue; gasmix = dp->gasmix; depthvalue = get_depth_units(dp->depth, &decimals, &depth_unit); /* analyze the dive points ahead */ nextdp = dp->next; while (nextdp && nextdp->time == 0) nextdp = nextdp->next; if (nextdp) newgasmix = nextdp->gasmix; /* do we want to skip this leg as it is devoid of anything useful? */ if (!dp->entered && gasmix_distance(&gasmix, &newgasmix) == 0 && nextdp && dp->depth != lastdepth && nextdp->depth != dp->depth) continue; if (dp->time - lasttime < 10 && !(gaschange && dp->next && dp->depth != dp->next->depth)) continue; len = strlen(buffer); if (nextdp && gasmix_distance(&gasmix, &newgasmix)) gaschange = true; if (plan_verbatim) { if (dp->depth != lastdepth) { if (plan_display_transitions || dp->entered || !dp->next || (gaschange && dp->next && dp->depth != nextdp->depth)) { snprintf(temp, sizeof(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, sizeof(buffer) - len, "%s<br>", temp); lasttime = dp->time; } } else { if (dp->depth != nextdp->depth) { snprintf(temp, sizeof(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, sizeof(buffer) - len, "%s<br>", temp); lasttime = dp->time; } } } else { if ((dp->depth == lastdepth && dp->depth != nextdp->depth) || plan_display_transitions || dp->entered || !dp->next || (gaschange && dp->next && dp->depth != nextdp->depth)) { snprintf(temp, sizeof(temp), translate("gettextFromC", "%3.0f%s"), depthvalue, depth_unit); len += snprintf(buffer + len, sizeof(buffer) - len, "<tr><td style='padding-left: 10px; float: right;'>%s</td>", temp); if (plan_display_runtime) { snprintf(temp, sizeof(temp), translate("gettextFromC", "%3dmin"), (dp->time + 30) / 60); len += snprintf(buffer + len, sizeof(buffer) - len, "<td style='padding-left: 10px; float: right;'>%s</td>", temp); } if (plan_display_duration) { snprintf(temp, sizeof(temp), translate("gettextFromC", "%3dmin"), (dp->time - lasttime + 30) / 60); len += snprintf(buffer + len, sizeof(buffer) - len, "<td style='padding-left: 10px; float: right;'>%s</td>", temp); } if (gaschange) { len += snprintf(buffer + len, sizeof(buffer) - len, "<td style='padding-left: 10px; color: red; float: left;'><b>%s</b></td>", gasname(&newgasmix)); gaschange = false; } else { len += snprintf(buffer + len, sizeof(buffer) - len, "<td> </td>"); } len += snprintf(buffer + len, sizeof(buffer) - len, "</tr>"); lasttime = dp->time; } } if (gaschange) { // gas switch at this waypoint if (plan_verbatim) { snprintf(temp, sizeof(temp), translate("gettextFromC", "Switch gas to %s"), gasname(&newgasmix)); len += snprintf(buffer + len, sizeof(buffer) - len, "%s<br>", temp); gaschange = false; } gasmix = newgasmix; } lastdepth = dp->depth; } while ((dp = nextdp) != NULL); len += snprintf(buffer + len, sizeof(buffer) - len, "</tbody></table></div>"); dive->cns = 0; dive->maxcns = 0; update_cylinder_related_info(dive); snprintf(temp, sizeof(temp), "%s", translate("gettextFromC", "CNS")); len += snprintf(buffer + len, sizeof(buffer) - len, "<div><br>%s: %i%%", temp, dive->cns); snprintf(temp, sizeof(temp), "%s", translate("gettextFromC", "OTU")); len += snprintf(buffer + len, sizeof(buffer) - len, "<br>%s: %i</div>", temp, dive->otu); snprintf(temp, sizeof(temp), "%s", translate("gettextFromC", "Gas consumption:")); len += snprintf(buffer + len, sizeof(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, sizeof(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, sizeof(temp), translate("gettextFromC", "%.0f%s (%.0f%s during planned ascent) of %s"), volume, unit, deco_volume, unit, gasname(&cyl->gasmix)); } len += snprintf(buffer + len, sizeof(buffer) - len, "%s%s<br>", temp, warning); } dp = diveplan->dp; while (dp) { if (dp->time != 0) { int pO2 = depth_to_atm(dp->depth, dive) * get_o2(&dp->gasmix); if (pO2 > (dp->entered ? prefs.bottompo2 : prefs.decopo2)) { const char *depth_unit; int decimals; double depth_value = get_depth_units(dp->depth, &decimals, &depth_unit); len = strlen(buffer); snprintf(temp, sizeof(temp), translate("gettextFromC", "high pO₂ value %.2f at %d:%02u with gas %s at depth %.*f %s"), pO2 / 1000.0, FRACTION(dp->time, 60), gasname(&dp->gasmix), decimals, depth_value, depth_unit); len += snprintf(buffer + len, sizeof(buffer) - len, "<span style='color: red;'>%s </span> %s<br>", translate("gettextFromC", "Warning:"), temp); } } dp = dp->next; } snprintf(buffer + len, sizeof(buffer) - len, "</div>"); dive->notes = strdup(buffer); }
/* 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, 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) < 200) return i; } fprintf(stderr, "this gas %s should have been on the cylinder list\nThings will fail now\n", gasname(&mix_in)); return -1; }
QVariant ProfilePrintModel::data(const QModelIndex &index, int role) const { const int row = index.row(); const int col = index.column(); switch (role) { case Qt::DisplayRole: { struct dive *dive = get_dive_by_uniq_id(diveId); struct DiveItem di; di.diveId = diveId; const QString unknown = tr("unknown"); // dive# + date, depth, location, duration if (row == 0) { if (col == 0) return tr("Dive #%1 - %2").arg(dive->number).arg(di.displayDate()); if (col == 3) { QString unit = (get_units()->length == units::METERS) ? "m" : "ft"; return tr("Max depth: %1 %2").arg(di.displayDepth()).arg(unit); } } if (row == 1) { if (col == 0) return QString(dive->location); if (col == 3) return QString(tr("Duration: %1 min")).arg(di.displayDuration()); } // headings if (row == 2) { if (col == 0) return tr("Gas used:"); if (col == 2) return tr("Tags:"); if (col == 3) return tr("SAC:"); if (col == 4) return tr("Weights:"); } // notes if (col == 0) { if (row == 6) return tr("Notes:"); if (row == 7) return QString(dive->notes); } // more headings if (row == 4) { if (col == 0) return tr("Divemaster:"); if (col == 1) return tr("Buddy:"); if (col == 2) return tr("Suit:"); if (col == 3) return tr("Viz:"); if (col == 4) return tr("Rating:"); } // values for gas, sac, etc... if (row == 3) { if (col == 0) { int added = 0; QString gas, gases; for (int i = 0; i < MAX_CYLINDERS; i++) { if (!is_cylinder_used(dive, i)) continue; gas = dive->cylinder[i].type.description; gas += QString(!gas.isEmpty() ? " " : "") + gasname(&dive->cylinder[i].gasmix); // if has a description and if such gas is not already present if (!gas.isEmpty() && gases.indexOf(gas) == -1) { if (added > 0) gases += QString(" / "); gases += gas; added++; } } return gases; } if (col == 2) { char buffer[256]; taglist_get_tagstring(dive->tag_list, buffer, 256); return QString(buffer); } if (col == 3) return di.displaySac(); if (col == 4) { weight_t tw = { total_weight(dive) }; return get_weight_string(tw, true); } } // values for DM, buddy, suit, etc... if (row == 5) { if (col == 0) return QString(dive->divemaster); if (col == 1) return QString(dive->buddy); if (col == 2) return QString(dive->suit); if (col == 3) return (dive->visibility) ? QString::number(dive->visibility).append(" / 5") : QString(); if (col == 4) return (dive->rating) ? QString::number(dive->rating).append(" / 5") : QString(); } return QString(); } case Qt::FontRole: { QFont font; font.setPointSizeF(fontSize); if (row == 0 && col == 0) { font.setBold(true); } return QVariant::fromValue(font); } case Qt::TextAlignmentRole: { // everything is aligned to the left unsigned int align = Qt::AlignLeft; // align depth and duration right if (row < 2 && col == 4) align = Qt::AlignRight | Qt::AlignVCenter; return QVariant::fromValue(align); } } // switch (role) return QVariant(); }
bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer) { struct sample *sample; int po2; int transitiontime, gi; int current_cylinder; unsigned int stopidx; int depth; double tissue_tolerance = 0.0; struct gaschanges *gaschanges = NULL; int gaschangenr; unsigned int *stoplevels = NULL; bool stopping = false; bool clear_to_ascend; 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; 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; create_dive_from_plan(diveplan, is_planner); if (prefs.verbatim_plan) plan_verbatim = true; if (prefs.display_runtime) plan_display_runtime = true; if (prefs.display_duration) plan_display_duration = true; if (prefs.display_transitions) plan_display_transitions = true; if (prefs.last_stop) decostoplevels[1] = 6000; /* 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); } tissue_tolerance = tissue_at_end(&displayed_dive, cached_datap); #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 < sizeof(decostoplevels) / sizeof(int); 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; if(prefs.recreational_mode) { 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? while (trial_ascent(depth, 0, avg_depth, bottom_time, tissue_tolerance, &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0) && enough_gas(current_cylinder)) { tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &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; } 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; tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, TIMESTEP, po2, &displayed_dive, prefs.decosac); 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) { stopping = true; 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 } 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) { 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]; tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &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 */ 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 */ if (current_cylinder != gaschanges[gi].gasidx) { 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 */ tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac); clock += prefs.min_switch_duration; } 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, tissue_tolerance, &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 */ plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; stopping = true; } /* Deco stop should end when runtime is at a whole minute */ int this_decotimestep; this_decotimestep = DECOTIMESTEP - clock % DECOTIMESTEP; tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &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) { if (get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) { o2time += DECOTIMESTEP; if (o2time >= 12 * 60) { breaktime = 0; breakcylinder = current_cylinder; 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; 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 */ plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); previous_point_time = clock; stopping = false; } } /* We made it to the surface * Create the final dive, add the plan to the notes and fixup some internal * data that we need to be there when plotting the dive */ 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 decodive; }
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; unsigned int stopidx; int depth; double tissue_tolerance = 0.0; struct gaschanges *gaschanges = NULL; int gaschangenr; int *decostoplevels; int decostoplevelcount; unsigned int *stoplevels = NULL; int vpmb_first_stop; bool stopping = false; bool pendinggaschange = false; bool clear_to_ascend; 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; 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); } calc_crushing_pressure(depth_to_mbar(depth, &displayed_dive) / 1000.0); nuclear_regeneration(clock); clear_deco(displayed_dive.surface_pressure.mbar / 1000.0); vpmb_start_gradient(); previous_deco_time = 100000000; deco_time = 10000000; tissue_tolerance = tissue_at_end(&displayed_dive, cached_datap); displayed_dive.surface_pressure.mbar = diveplan->surface_pressure; #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; 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? while (trial_ascent(depth, 0, avg_depth, bottom_time, tissue_tolerance, &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0) && enough_gas(current_cylinder)) { tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &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; } 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; tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &displayed_dive.cylinder[current_cylinder].gasmix, TIMESTEP, po2, &displayed_dive, prefs.decosac); 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) { stopping = true; 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 nuclear_regeneration(clock); vpmb_start_gradient(); previous_deco_time = 100000000; deco_time = 10000000; cache_deco_state(tissue_tolerance, &bottom_cache); // Lets us make several iterations bottom_depth = depth; bottom_gi = gi; bottom_gas = gas; bottom_stopidx = stopidx; // Find first stop used for VPM-B Boyle's law compensation if (prefs.deco_mode == VPMB) { vpmb_first_stop = deco_allowed_depth(tissue_tolerance, diveplan->surface_pressure / 1000, &displayed_dive, 1); if (vpmb_first_stop > 0) { while (stoplevels[stopidx] > vpmb_first_stop) { stopidx--; } stopidx++; vpmb_first_stop = stoplevels[stopidx]; } first_stop_pressure.mbar = depth_to_mbar(vpmb_first_stop, &displayed_dive); } else { first_stop_pressure.mbar = 0; } //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; tissue_tolerance = 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; 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]; tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &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; // Boyles Law compensation boyles_law(depth_to_mbar(stoplevels[stopidx], &displayed_dive) / 1000.0); /* 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, tissue_tolerance, &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 */ tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &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, tissue_tolerance, &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; // Boyles Law compensation boyles_law(depth_to_mbar(stoplevels[stopidx], &displayed_dive) / 1000.0); } /* 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 */ tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &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; tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0, &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); return decodive; }
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 buf[1000], *deco; 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; 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 Buhlmann ZHL-16B with GFlow = %d and GFhigh = %d"), diveplan->gflow, diveplan->gfhigh); } else if (prefs.deco_mode == VPMB){ snprintf(temp, sz_temp, "%s", translate("gettextFromC", "based on VPM-B")); } else if (prefs.deco_mode == RECREATIONAL){ snprintf(temp, sz_temp, translate("gettextFromC", "recreational mode based on Buhlmann 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>%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 )) { snprintf(temp, sz_temp, translate("gettextFromC", "%3.0f%s"), depthvalue, depth_unit); len += snprintf(buffer + len, sz_buffer - len, "<tr><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); 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); }