Minimum gas calculation - Calculations and UI parameters

Add minimum gas calculation to planner output.
Add the two UI parameters prefs.sacfactor and prefs.problemsolvingtime.
Connect UI signals and slots for recalculation of diveplan.

Disable minimum gas calculation if there was already a warning before.
If minimum gas result is larger then cylinder start pressure give warning message instead of result.

Add line break before pO2 warnings but only if warnings exist.

Signed-off-by: Joachim Ritter <jritter@bitsenke.de>
Signed-off-by: Stefan Fuchs <sfuchs@gmx.de>
This commit is contained in:
Stefan Fuchs 2017-02-11 20:24:18 +01:00 committed by Dirk Hohndel
parent b8e044dee3
commit 7f8c3592ce
10 changed files with 240 additions and 45 deletions

View file

@ -546,6 +546,7 @@ static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool
bool gaschange_before;
bool lastentered = true;
struct divedatapoint *nextdp = NULL;
struct divedatapoint *lastbottomdp = NULL;
plan_verbatim = prefs.verbatim_plan;
plan_display_runtime = prefs.display_runtime;
@ -639,6 +640,17 @@ static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool
if (dp->time - lasttime < 10 && !(gaschange_after && dp->next && dp->depth != dp->next->depth))
continue;
/* Store pointer to last entered datapoint for minimum gas calculation */
/* Do this only if depth is larger than last/2nd last deco stop at ~6m */
int secondlastdecostop = 0;
if (prefs.units.length == METERS ) {
secondlastdecostop = decostoplevels_metric[2];
} else {
secondlastdecostop = decostoplevels_imperial[2];
}
if (dp->entered && !nextdp->entered && dp->depth > secondlastdecostop)
lastbottomdp = dp;
len = strlen(buffer);
if (plan_verbatim) {
/* When displaying a verbatim plan, we output a waypoint for every gas change.
@ -847,10 +859,13 @@ static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool
snprintf(temp, sz_temp, "%s %.*f|%.*f%s/min):", translate("gettextFromC", "Gas consumption (based on SAC"),
sacdecimals, bottomsacvalue, sacdecimals, decosacvalue, sacunit);
len += snprintf(buffer + len, sz_buffer - len, "<div>%s<br>", temp);
/* Print gas consumption: This loop covers all cylinders */
for (int gasidx = 0; gasidx < MAX_CYLINDERS; gasidx++) {
double volume, pressure, deco_volume, deco_pressure;
const char *unit, *pressure_unit;
double volume, pressure, deco_volume, deco_pressure, mingas_volume, mingas_pressure, mingas_depth;
const char *unit, *pressure_unit, *depth_unit;
char warning[1000] = "";
char mingas[1000] = "";
cylinder_t *cyl = &dive->cylinder[gasidx];
if (cylinder_none(cyl))
break;
@ -867,23 +882,59 @@ static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool
* This only works if we have working pressure for the cylinder
* 10bar is a made up number - but it seemed silly to pretend you could breathe cylinder down to 0 */
if (cyl->end.mbar < 10000)
snprintf(warning, sizeof(warning), " &mdash; <span style='color: red;'>%s </span> %s",
snprintf(warning, sizeof(warning), "<br>&nbsp;&mdash; <span style='color: red;'>%s </span> %s",
translate("gettextFromC", "Warning:"),
translate("gettextFromC", "this is more gas than available in the specified cylinder!"));
else
if ((float) cyl->end.mbar * cyl->type.size.mliter / 1000.0 / gas_compressibility_factor(&cyl->gasmix, cyl->end.mbar / 1000.0)
< (float) cyl->deco_gas_used.mliter)
snprintf(warning, sizeof(warning), " &mdash; <span style='color: red;'>%s </span> %s",
snprintf(warning, sizeof(warning), "<br>&nbsp;&mdash; <span style='color: red;'>%s </span> %s",
translate("gettextFromC", "Warning:"),
translate("gettextFromC", "not enough reserve for gas sharing on ascent!"));
snprintf(temp, sz_temp, translate("gettextFromC", "%.0f%s/%.0f%s of %s (%.0f%s/%.0f%s in planned ascent)"), volume, unit, pressure, pressure_unit, gasname(&cyl->gasmix), deco_volume, unit, deco_pressure, pressure_unit);
/* Do and print minimum gas calculation for last bottom gas, but only for OC mode */
/* and if no other warning was set before. */
else
if (lastbottomdp && gasidx == lastbottomdp->cylinderid
&& dive->dc.divemode == OC) {
/* Calculate minimum gas volume. */
volume_t mingasv;
mingasv.mliter = prefs.problemsolvingtime * prefs.bottomsac * prefs.sacfactor / 100.0
* depth_to_bar(lastbottomdp->depth, dive)
+ cyl->deco_gas_used.mliter * prefs.sacfactor / 100.0;
/* Calculate minimum gas pressure for cyclinder. */
pressure_t mingasp;
mingasp.mbar = isothermal_pressure(&cyl->gasmix, 1.0,
mingasv.mliter, cyl->type.size.mliter) * 1000;
/* Translate all results into correct units */
mingas_volume = get_volume_units(mingasv.mliter, NULL, &unit);
mingas_pressure = get_pressure_units(mingasp.mbar, &pressure_unit);
mingas_depth = get_depth_units(lastbottomdp->depth, NULL, &depth_unit);
/* Print it to results */
if (cyl->start.mbar > mingasp.mbar) snprintf(mingas, sizeof(mingas),
translate("gettextFromC", "<br>&nbsp;&mdash; <span style='color: green;'>Minimum gas</span> (based on %.1fxSAC/+%dmin@%.0f%s): %.0f%s/%.0f%s"),
prefs.sacfactor / 100.0, prefs.problemsolvingtime,
mingas_depth, depth_unit,
mingas_volume, unit,
mingas_pressure, pressure_unit);
else snprintf(warning, sizeof(warning), "<br>&nbsp;&mdash; <span style='color: red;'>%s </span> %s",
translate("gettextFromC", "Warning:"),
translate("gettextFromC", "required minimum gas for ascent already exceeding start pressure of cylinder!"));
}
/* Print the gas consumption for every cylinder here to temp buffer. */
snprintf(temp, sz_temp, translate("gettextFromC", "%.0f%s/%.0f%s of <span style='color: red;'><b>%s</b></span> (%.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));
snprintf(temp, sz_temp, translate("gettextFromC", "%.0f%s (%.0f%s during planned ascent) of <span style='color: red;'><b>%s</b></span>"),
volume, unit, deco_volume, unit, gasname(&cyl->gasmix));
}
len += snprintf(buffer + len, sz_buffer - len, "%s%s<br>", temp, warning);
/* Gas consumption: Now finally print all strings to output */
len += snprintf(buffer + len, sz_buffer - len, "%s%s%s<br>", temp, warning, mingas);
}
/* Print warnings for pO2 */
dp = diveplan->dp;
bool o2warning_exist = false;
if (dive->dc.divemode != CCR) {
while (dp) {
if (dp->time != 0) {
@ -896,6 +947,8 @@ static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool
int decimals;
double depth_value = get_depth_units(dp->depth, &decimals, &depth_unit);
len = strlen(buffer);
if (!o2warning_exist) len += snprintf(buffer + len, sz_buffer - len, "<br>");
o2warning_exist = true;
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(gasmix), decimals, depth_value, depth_unit);
@ -906,6 +959,8 @@ static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool
int decimals;
double depth_value = get_depth_units(dp->depth, &decimals, &depth_unit);
len = strlen(buffer);
if (!o2warning_exist) len += snprintf(buffer + len, sz_buffer - len, "<br>");
o2warning_exist = true;
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(gasmix), decimals, depth_value, depth_unit);

View file

@ -105,6 +105,8 @@ struct preferences {
int ascratestops;
int ascratelast6m;
int descrate;
int sacfactor;
int problemsolvingtime;
int bottompo2;
int decopo2;
enum deco_mode display_deco_mode;

View file

@ -1240,6 +1240,16 @@ int DivePlannerSettings::descrate() const
return prefs.descrate;
}
int DivePlannerSettings::sacfactor() const
{
return prefs.sacfactor;
}
int DivePlannerSettings::problemsolvingtime() const
{
return prefs.problemsolvingtime;
}
int DivePlannerSettings::bottompo2() const
{
return prefs.bottompo2;
@ -1441,6 +1451,30 @@ void DivePlannerSettings::setDescrate(int value)
emit descrateChanged(value);
}
void DivePlannerSettings::setSacFactor(int value)
{
if (value == prefs.sacfactor)
return;
QSettings s;
s.beginGroup(group);
s.setValue("sacfactor", value);
prefs.sacfactor = value;
emit sacFactorChanged(value);
}
void DivePlannerSettings::setProblemSolvingTime(int value)
{
if (value == prefs.problemsolvingtime)
return;
QSettings s;
s.beginGroup(group);
s.setValue("problemsolvingtime", value);
prefs.problemsolvingtime = value;
emit problemSolvingTimeChanged(value);
}
void DivePlannerSettings::setBottompo2(int value)
{
if (value == prefs.bottompo2)
@ -2279,6 +2313,8 @@ void SettingsObjectWrapper::load()
GET_INT("ascratestops", ascratestops);
GET_INT("ascratelast6m", ascratelast6m);
GET_INT("descrate", descrate);
GET_INT("sacfactor", sacfactor);
GET_INT("problemsolvingtime", problemsolvingtime);
GET_INT("bottompo2", bottompo2);
GET_INT("decopo2", decopo2);
GET_INT("bestmixend", bestmixend.mm);
@ -2331,6 +2367,8 @@ void SettingsObjectWrapper::sync()
s.setValue("ascratestops", prefs.ascratestops);
s.setValue("ascratelast6m", prefs.ascratelast6m);
s.setValue("descrate", prefs.descrate);
s.setValue("sacfactor", prefs.sacfactor);
s.setValue("problemsolvingtime", prefs.problemsolvingtime);
s.setValue("bottompo2", prefs.bottompo2);
s.setValue("decopo2", prefs.decopo2);
s.setValue("bestmixend", prefs.bestmixend.mm);

View file

@ -402,6 +402,8 @@ class DivePlannerSettings : public QObject {
Q_PROPERTY(int ascratestops READ ascratestops WRITE setAscratestops NOTIFY ascratestopsChanged)
Q_PROPERTY(int ascratelast6m READ ascratelast6m WRITE setAscratelast6m NOTIFY ascratelast6mChanged)
Q_PROPERTY(int descrate READ descrate WRITE setDescrate NOTIFY descrateChanged)
Q_PROPERTY(int sacfactor READ sacfactor WRITE setSacFactor NOTIFY sacFactorChanged)
Q_PROPERTY(int problemsolvingtime READ problemsolvingtime WRITE setProblemSolvingTime NOTIFY problemSolvingTimeChanged)
Q_PROPERTY(int bottompo2 READ bottompo2 WRITE setBottompo2 NOTIFY bottompo2Changed)
Q_PROPERTY(int decopo2 READ decopo2 WRITE setDecopo2 NOTIFY decopo2Changed)
Q_PROPERTY(int bestmixend READ bestmixend WRITE setBestmixend NOTIFY bestmixendChanged)
@ -427,6 +429,8 @@ public:
int ascratestops() const;
int ascratelast6m() const;
int descrate() const;
int sacfactor() const;
int problemsolvingtime() const;
int bottompo2() const;
int decopo2() const;
int bestmixend() const;
@ -451,6 +455,8 @@ public slots:
void setAscratestops(int value);
void setAscratelast6m(int value);
void setDescrate(int value);
void setSacFactor(int value);
void setProblemSolvingTime(int value);
void setBottompo2(int value);
void setDecopo2(int value);
void setBestmixend(int value);
@ -475,6 +481,8 @@ signals:
void ascratestopsChanged(int value);
void ascratelast6mChanged(int value);
void descrateChanged(int value);
void sacFactorChanged(int value);
void problemSolvingTimeChanged(int value);
void bottompo2Changed(int value);
void decopo2Changed(int value);
void bestmixendChanged(int value);

View file

@ -50,6 +50,8 @@ struct preferences default_prefs = {
.ascratestops = 6000 / 60,
.ascratelast6m = 1000 / 60,
.descrate = 18000 / 60,
.sacfactor = 400,
.problemsolvingtime = 4,
.bottompo2 = 1400,
.decopo2 = 1600,
.bestmixend.mm = 30000,

View file

@ -300,6 +300,8 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f)
ui.display_runtime->setChecked(prefs.display_runtime);
ui.display_transitions->setChecked(prefs.display_transitions);
ui.safetystop->setChecked(prefs.safetystop);
ui.sacfactor->setValue(prefs.sacfactor / 100.0);
ui.problemsolvingtime->setValue(prefs.problemsolvingtime);
ui.bottompo2->setValue(prefs.bottompo2 / 1000.0);
ui.decopo2->setValue(prefs.decopo2 / 1000.0);
ui.backgasBreaks->setChecked(prefs.doo2breaks);
@ -362,6 +364,8 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f)
connect(ui.descRate, SIGNAL(valueChanged(int)), this, SLOT(setDescRate(int)));
connect(ui.ascRateStops, SIGNAL(valueChanged(int)), this, SLOT(setAscRateStops(int)));
connect(ui.ascRateLast6m, SIGNAL(valueChanged(int)), this, SLOT(setAscRateLast6m(int)));
connect(ui.sacfactor, SIGNAL(valueChanged(double)), this, SLOT(sacFactorChanged(double)));
connect(ui.problemsolvingtime, SIGNAL(valueChanged(int)), this, SLOT(problemSolvingTimeChanged(int)));
connect(ui.bottompo2, SIGNAL(valueChanged(double)), this, SLOT(setBottomPo2(double)));
connect(ui.decopo2, SIGNAL(valueChanged(double)), this, SLOT(setDecoPo2(double)));
connect(ui.bestmixEND, SIGNAL(valueChanged(int)), this, SLOT(setBestmixEND(int)));
@ -480,6 +484,16 @@ void PlannerSettingsWidget::setDescRate(int rate)
SettingsObjectWrapper::instance()->planner_settings->setDescrate(rate * UNIT_FACTOR);
}
void PlannerSettingsWidget::sacFactorChanged(const double factor)
{
plannerModel->setSacFactor(factor);
}
void PlannerSettingsWidget::problemSolvingTimeChanged(const int minutes)
{
plannerModel->setProblemSolvingTime(minutes);
}
void PlannerSettingsWidget::setBottomPo2(double po2)
{
SettingsObjectWrapper::instance()->planner_settings->setBottompo2((int) (po2 * 1000.0));

View file

@ -78,6 +78,8 @@ slots:
void setAscRateStops(int rate);
void setAscRateLast6m(int rate);
void setDescRate(int rate);
void sacFactorChanged(const double factor);
void problemSolvingTimeChanged(const int min);
void setBottomPo2(double po2);
void setDecoPo2(double po2);
void setBestmixEND(int depth);

View file

@ -402,7 +402,7 @@
</item>
<item row="19" column="1">
<widget class="QComboBox" name="rebreathermode">
<property name="currentText" stdset="0">
<property name="currentText">
<string/>
</property>
<property name="maxVisibleItems">
@ -530,7 +530,40 @@
<property name="spacing">
<number>2</number>
</property>
<item row="5" column="0">
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="decoStopSAC">
<property name="suffix">
<string>/min</string>
</property>
<property name="decimals">
<number>0</number>
</property>
<property name="maximum">
<double>99.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="decosac">
<property name="text">
<string>Deco SAC</string>
</property>
</widget>
</item>
<item row="8" column="0">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -543,7 +576,7 @@
</property>
</spacer>
</item>
<item row="2" column="1">
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="bottompo2">
<property name="suffix">
<string>bar</string>
@ -572,7 +605,7 @@
</property>
</widget>
</item>
<item row="3" column="1">
<item row="5" column="1">
<widget class="QDoubleSpinBox" name="decopo2">
<property name="suffix">
<string>bar</string>
@ -588,8 +621,11 @@
</property>
</widget>
</item>
<item row="4" column="1">
<item row="6" column="1">
<widget class="QSpinBox" name="bestmixEND">
<property name="toolTip">
<string>Used to calculate best mix. Select best mix depth in 'Available gases' table by entering gas depth, followed by &quot;B&quot; (best trimix mix) or &quot;BN&quot; (best nitrox mix)</string>
</property>
<property name="suffix">
<string>m</string>
</property>
@ -602,9 +638,6 @@
<property name="value">
<number>30</number>
</property>
<property name="toolTip">
<string>Used to calculate best mix. Select best mix depth in 'Available gases' table by entering gas depth, followed by "B" (best trimix mix) or "BN" (best nitrox mix)</string>
</property>
</widget>
</item>
<item row="0" column="0">
@ -614,21 +647,21 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="4" column="0">
<widget class="QLabel" name="bottompO2">
<property name="text">
<string>Bottom pO₂</string>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="6" column="0">
<widget class="QLabel" name="bestEND">
<property name="text">
<string>Best mix END</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<item row="8" column="0" colspan="2">
<widget class="QGroupBox" name="Notes">
<property name="title">
<string>Notes</string>
@ -692,45 +725,67 @@
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="decoStopSAC">
<property name="suffix">
<string>/min</string>
</property>
<property name="decimals">
<number>0</number>
</property>
<property name="maximum">
<double>99.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="5" column="0">
<widget class="QLabel" name="decopO2">
<property name="text">
<string>Deco pO₂</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="decosac">
<item row="2" column="0">
<widget class="QLabel" name="sacFactor">
<property name="text">
<string>Deco SAC</string>
<string>SAC factor</string>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="sacfactor">
<property name="toolTip">
<string>Used to calculate minimum gas. Consider two divers with possibly increased SAC after OoG event.</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
<property name="decimals">
<number>1</number>
</property>
</spacer>
<property name="minimum">
<double>2.000000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>4.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="problemSolvingTime">
<property name="text">
<string>Problem solving time</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="problemsolvingtime">
<property name="toolTip">
<string>Used to calculate minimum gas. Additional time at max. depth after OoG event.</string>
</property>
<property name="suffix">
<string>min</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="maximum">
<number>10</number>
</property>
<property name="value">
<number>4</number>
</property>
</widget>
</item>
</layout>
</widget>
@ -755,6 +810,7 @@
<tabstop>gflow</tabstop>
<tabstop>gfhigh</tabstop>
<tabstop>vpmb_deco</tabstop>
<tabstop>vpmb_conservatism</tabstop>
<tabstop>drop_stone_mode</tabstop>
<tabstop>lastStop</tabstop>
<tabstop>backgasBreaks</tabstop>
@ -763,6 +819,8 @@
<tabstop>rebreathermode</tabstop>
<tabstop>bottomSAC</tabstop>
<tabstop>decoStopSAC</tabstop>
<tabstop>sacfactor</tabstop>
<tabstop>problemsolvingtime</tabstop>
<tabstop>bottompo2</tabstop>
<tabstop>decopo2</tabstop>
<tabstop>bestmixEND</tabstop>

View file

@ -401,6 +401,20 @@ void DivePlannerPointsModel::setDecoSac(double sac)
emitDataChanged();
}
void DivePlannerPointsModel::setSacFactor(double factor)
{
auto planner = SettingsObjectWrapper::instance()->planner_settings;
planner->setSacFactor((int) round(factor * 100));
emitDataChanged();
}
void DivePlannerPointsModel::setProblemSolvingTime(int minutes)
{
auto planner = SettingsObjectWrapper::instance()->planner_settings;
planner->setProblemSolvingTime(minutes);
emitDataChanged();
}
void DivePlannerPointsModel::setGFHigh(const int gfhigh)
{
tempGFHigh = gfhigh;

View file

@ -91,6 +91,8 @@ slots:
void setReserveGas(int reserve);
void setSwitchAtReqStop(bool value);
void setMinSwitchDuration(int duration);
void setSacFactor(double factor);
void setProblemSolvingTime(int minutes);
signals:
void planCreated();