bool DivePlannerPointsModel::addGas(struct gasmix mix) { sanitize_gasmix(&mix); for (int i = 0; i < MAX_CYLINDERS; i++) { cylinder_t *cyl = &displayed_dive.cylinder[i]; if (cylinder_nodata(cyl)) { fill_default_cylinder(cyl); cyl->gasmix = mix; /* The depth to change to that gas is given by the depth where its pO₂ is 1.6 bar. * The user should be able to change this depth manually. */ pressure_t modpO2; modpO2.mbar = prefs.decopo2; cyl->depth = gas_mod(&mix, modpO2, M_OR_FT(3,10)); // FIXME -- need to get rid of stagingDIve // the following now uses displayed_dive !!!! CylindersModel::instance()->updateDive(); return true; } if (!gasmix_distance(&cyl->gasmix, &mix)) return true; } qDebug("too many gases"); return false; }
void DivePlannerPointsModel::tanksUpdated() { // we don't know exactly what changed - what we care about is // "did a gas change on us". So we look through the diveplan to // see if there is a gas that is now missing and if there is, we // replace it with the matching new gas. QVector<QPair<int, int> > gases = collectGases(&displayed_dive); if (gases.count() == oldGases.count()) { // either nothing relevant changed, or exactly ONE gasmix changed for (int i = 0; i < gases.count(); i++) { if (gases.at(i) != oldGases.at(i)) { if (oldGases.count(oldGases.at(i)) > 1) { // we had this gas more than once, so don't // change segments that used this gas as it still exists break; } for (int j = 0; j < rowCount(); j++) { divedatapoint &p = divepoints[j]; struct gasmix gas; gas.o2.permille = oldGases.at(i).first; gas.he.permille = oldGases.at(i).second; if (gasmix_distance(&gas, &p.gasmix) < 100) { p.gasmix.o2.permille = gases.at(i).first; p.gasmix.he.permille = gases.at(i).second; } } break; } } } emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); }
int get_gasidx(struct dive *dive, struct gasmix *mix) { int gasidx = -1; while (++gasidx < MAX_CYLINDERS) if (gasmix_distance(&dive->cylinder[gasidx].gasmix, mix) < 200) return gasidx; return -1; }
void DivePlannerPointsModel::gaschange(const QModelIndex &index, QString newgas) { int i = index.row(); gasmix oldgas = divepoints[i].gasmix; gasmix gas = { 0 }; if (!validate_gas(newgas.toUtf8().data(), &gas)) return; while (i < rowCount() && gasmix_distance(&oldgas, &divepoints[i].gasmix) == 0) divepoints[i++].gasmix = gas; emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1)); }
/* look at all dive computers and figure out if this cylinder is used anywhere * d has to be a valid dive (test before calling) * cyl does not have to be a cylinder that is part of this dive structure */ bool cylinder_is_used(struct dive *d, cylinder_t *cyl) { struct divecomputer *dc = &d->dc; bool same_as_first = gasmix_distance(&cyl->gasmix, &d->cylinder[0].gasmix) < 200; while (dc) { struct event *ev = get_next_event(dc->events, "gaschange"); if (same_as_first && (!ev || ev->time.seconds > 30)) { // unless there is a gas change in the first 30 seconds we can // always mark the first cylinder as used return true; } while (ev) { if (gasmix_distance(&cyl->gasmix, get_gasmix_from_event(ev)) < 200) return true; ev = get_next_event(ev->next, "gaschange"); } dc = dc->next; } return false; }
bool DivePlannerPointsModel::tankInUse(struct gasmix gasmix) { for (int j = 0; j < rowCount(); j++) { divedatapoint &p = divepoints[j]; if (p.time == 0) // special entries that hold the available gases continue; if (!p.entered) // removing deco gases is ok continue; if (gasmix_distance(&p.gasmix, &gasmix) < 100) return true; } return false; }
/* 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; }
void CylindersModel::remove(const QModelIndex &index) { if (index.column() != REMOVE) { return; } int same_gas = -1; cylinder_t *cyl = &displayed_dive.cylinder[index.row()]; struct gasmix *mygas = &cyl->gasmix; for (int i = 0; i < MAX_CYLINDERS; i++) { if (i == index.row() || cylinder_none(&displayed_dive.cylinder[i])) continue; struct gasmix *gas2 = &displayed_dive.cylinder[i].gasmix; if (gasmix_distance(mygas, gas2) == 0) same_gas = i; } if (same_gas == -1 && ((DivePlannerPointsModel::instance()->currentMode() != DivePlannerPointsModel::NOTHING && DivePlannerPointsModel::instance()->tankInUse(cyl->gasmix)) || (DivePlannerPointsModel::instance()->currentMode() == DivePlannerPointsModel::NOTHING && (cyl->manually_added || is_cylinder_used(&displayed_dive, index.row()))))) { QMessageBox::warning(MainWindow::instance(), TITLE_OR_TEXT( tr("Cylinder cannot be removed"), tr("This gas is in use. Only cylinders that are not used in the dive can be removed.")), QMessageBox::Ok); return; } beginRemoveRows(QModelIndex(), index.row(), index.row()); // yah, know, ugly. rows--; if (index.row() == 0) { // first gas - we need to make sure that the same gas ends up // as first gas memmove(cyl, &displayed_dive.cylinder[same_gas], sizeof(*cyl)); remove_cylinder(&displayed_dive, same_gas); } else { remove_cylinder(&displayed_dive, index.row()); } changed = true; endRemoveRows(); }
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); }
/* 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; if (!diveplan || !diveplan->dp) return; #if DEBUG_PLAN & 4 printf("in create_dive_from_plan\n"); dump_plan(diveplan); #endif // 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; 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->po2.mbar = dp->po2; if (track_gas && cyl->type.workingpressure.mbar) sample->cylinderpressure.mbar = cyl->end.mbar; finish_sample(dc); while (dp) { struct gasmix gasmix = dp->gasmix; int po2 = dp->po2; 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) { if (lasttime) /* 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].po2.mbar = po2; sample->time.seconds = lasttime + 1; sample->depth.mm = lastdepth; 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].po2.mbar = po2; sample->po2.mbar = po2; sample->time.seconds = lasttime = time; sample->depth.mm = lastdepth = depth; if (track_gas) { 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; } #if DEBUG_PLAN & 32 save_dive(stdout, &displayed_dive); #endif return; gas_error_exit: report_error(translate("gettextFromC", "Too many gas mixes")); return; }
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); }