mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-30 22:20:21 +00:00
Introduce recreational planner mode
This adopts the planner to the needs of the recreational diver. Rather than immediately starting to ascent doing deco stops this mode, this mode stays at the last manually entered depth for the maximal time before mandantory stops appear (NDL). It does not change gas but keeps using the last used cylinder. TODO: * Grey out unused UI elements of the planner in this mode * Start ascent before gas runs out (or into reserve) * Do a 3min @ 5m safety stop. Fixes #840 Signed-off-by: Robert C. Helling <helling@atdotde.de> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
parent
297ddf666d
commit
8571dcf967
8 changed files with 135 additions and 71 deletions
89
planner.c
89
planner.c
|
@ -800,6 +800,31 @@ int ascend_velocity(int depth, int avg_depth, int bottom_time)
|
|||
}
|
||||
}
|
||||
|
||||
bool trial_ascent(int trial_depth, int stoplevel, int avg_depth, int bottom_time, double tissue_tolerance, struct gasmix *gasmix, int po2, double surface_pressure)
|
||||
{
|
||||
|
||||
bool clear_to_ascend = true;
|
||||
char *trial_cache = NULL;
|
||||
|
||||
cache_deco_state(tissue_tolerance, &trial_cache);
|
||||
while (trial_depth > stoplevel) {
|
||||
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,
|
||||
gasmix,
|
||||
TIMESTEP, po2, &displayed_dive, prefs.decosac);
|
||||
if (deco_allowed_depth(tissue_tolerance, surface_pressure, &displayed_dive, 1) > trial_depth - deltad) {
|
||||
/* We should have stopped */
|
||||
clear_to_ascend = false;
|
||||
break;
|
||||
}
|
||||
trial_depth -= deltad;
|
||||
}
|
||||
restore_deco_state(trial_cache);
|
||||
return clear_to_ascend;
|
||||
}
|
||||
|
||||
int plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer)
|
||||
{
|
||||
struct sample *sample;
|
||||
|
@ -812,7 +837,6 @@ int plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool s
|
|||
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;
|
||||
|
@ -880,6 +904,45 @@ int plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool s
|
|||
/* 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) {
|
||||
// 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)) {
|
||||
tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0,
|
||||
&displayed_dive.cylinder[current_cylinder].gasmix,
|
||||
DECOTIMESTEP, po2, &displayed_dive, prefs.bottomsac);
|
||||
clock += DECOTIMESTEP;
|
||||
}
|
||||
clock -= DECOTIMESTEP;
|
||||
plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false);
|
||||
previous_point_time = clock;
|
||||
do {
|
||||
/* Ascend to surface */
|
||||
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;
|
||||
last_ascend_rate = ascend_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;
|
||||
} 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(error);
|
||||
}
|
||||
|
||||
if (best_first_ascend_cylinder != current_cylinder) {
|
||||
stopping = true;
|
||||
|
@ -935,28 +998,10 @@ int plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool s
|
|||
--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, prefs.decosac);
|
||||
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)
|
||||
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 */
|
||||
|
@ -970,7 +1015,6 @@ int plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool s
|
|||
tissue_tolerance = add_segment(depth_to_mbar(depth, &displayed_dive) / 1000.0,
|
||||
&displayed_dive.cylinder[current_cylinder].gasmix,
|
||||
DECOTIMESTEP, po2, &displayed_dive, prefs.decosac);
|
||||
cache_deco_state(tissue_tolerance, &trial_cache);
|
||||
clock += DECOTIMESTEP;
|
||||
/* Finish infinite deco */
|
||||
if(clock >= 48 * 3600 && depth >= 6000) {
|
||||
|
@ -1002,7 +1046,6 @@ int plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool s
|
|||
}
|
||||
}
|
||||
}
|
||||
trial_depth = depth;
|
||||
}
|
||||
if (stopping) {
|
||||
/* Next we will ascend again. Add a waypoint if we have spend deco time */
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define PLANNER_H
|
||||
|
||||
#define LONGDECO 1
|
||||
#define NOT_RECREATIONAL 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
|
1
pref.h
1
pref.h
|
@ -78,6 +78,7 @@ struct preferences {
|
|||
bool display_runtime;
|
||||
bool display_duration;
|
||||
bool display_transitions;
|
||||
bool recreational_mode;
|
||||
int bottomsac;
|
||||
int decosac;
|
||||
int o2consumption; // ml per min
|
||||
|
|
|
@ -393,6 +393,7 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f)
|
|||
prefs.display_duration = s.value("display_duration", prefs.display_duration).toBool();
|
||||
prefs.display_runtime = s.value("display_runtime", prefs.display_runtime).toBool();
|
||||
prefs.display_transitions = s.value("display_transitions", prefs.display_transitions).toBool();
|
||||
prefs.recreational_mode = s.value("recreational_mode", prefs.recreational_mode).toBool();
|
||||
prefs.ascrate75 = s.value("ascrate75", prefs.ascrate75).toInt();
|
||||
prefs.ascrate50 = s.value("ascrate50", prefs.ascrate50).toInt();
|
||||
prefs.ascratestops = s.value("ascratestops", prefs.ascratestops).toInt();
|
||||
|
@ -414,6 +415,7 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f)
|
|||
ui.display_duration->setChecked(prefs.display_duration);
|
||||
ui.display_runtime->setChecked(prefs.display_runtime);
|
||||
ui.display_transitions->setChecked(prefs.display_transitions);
|
||||
ui.recreational_mode->setChecked(prefs.recreational_mode);
|
||||
ui.bottompo2->setValue(prefs.bottompo2 / 1000.0);
|
||||
ui.decopo2->setValue(prefs.decopo2 / 1000.0);
|
||||
ui.backgasBreaks->setChecked(prefs.doo2breaks);
|
||||
|
@ -427,6 +429,7 @@ PlannerSettingsWidget::PlannerSettingsWidget(QWidget *parent, Qt::WindowFlags f)
|
|||
connect(ui.display_duration, SIGNAL(toggled(bool)), plannerModel, SLOT(setDisplayDuration(bool)));
|
||||
connect(ui.display_runtime, SIGNAL(toggled(bool)), plannerModel, SLOT(setDisplayRuntime(bool)));
|
||||
connect(ui.display_transitions, SIGNAL(toggled(bool)), plannerModel, SLOT(setDisplayTransitions(bool)));
|
||||
connect(ui.recreational_mode, SIGNAL(toggled(bool)), plannerModel, SLOT(setRecreationalMode(bool)));
|
||||
connect(ui.ascRate75, SIGNAL(valueChanged(int)), this, SLOT(setAscRate75(int)));
|
||||
connect(ui.ascRate75, SIGNAL(valueChanged(int)), plannerModel, SLOT(emitDataChanged()));
|
||||
connect(ui.ascRate50, SIGNAL(valueChanged(int)), this, SLOT(setAscRate50(int)));
|
||||
|
@ -474,6 +477,7 @@ PlannerSettingsWidget::~PlannerSettingsWidget()
|
|||
s.setValue("display_duration", prefs.display_duration);
|
||||
s.setValue("display_runtime", prefs.display_runtime);
|
||||
s.setValue("display_transitions", prefs.display_transitions);
|
||||
s.setValue("recreational_mode", prefs.recreational_mode);
|
||||
s.setValue("ascrate75", prefs.ascrate75);
|
||||
s.setValue("ascrate50", prefs.ascrate50);
|
||||
s.setValue("ascratestops", prefs.ascratestops);
|
||||
|
@ -861,6 +865,12 @@ void DivePlannerPointsModel::setDisplayTransitions(bool value)
|
|||
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS - 1));
|
||||
}
|
||||
|
||||
void DivePlannerPointsModel::setRecreationalMode(bool value)
|
||||
{
|
||||
prefs.recreational_mode = value;
|
||||
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, COLUMNS -1));
|
||||
}
|
||||
|
||||
void DivePlannerPointsModel::setDropStoneMode(bool value)
|
||||
{
|
||||
prefs.drop_stone_mode = value;
|
||||
|
|
|
@ -82,6 +82,7 @@ slots:
|
|||
void setDisplayRuntime(bool value);
|
||||
void setDisplayDuration(bool value);
|
||||
void setDisplayTransitions(bool value);
|
||||
void setRecreationalMode(bool value);
|
||||
void savePlan();
|
||||
void saveDuplicatePlan();
|
||||
void remove(const QModelIndex &index);
|
||||
|
|
|
@ -262,61 +262,21 @@
|
|||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item row="0" column="2">
|
||||
<widget class="QSpinBox" name="gflow">
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>150</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>GF low</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>GF high</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="2">
|
||||
<item row="5" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="backgasBreaks">
|
||||
<property name="text">
|
||||
<string>Plan backgas breaks</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="lastStop">
|
||||
<property name="text">
|
||||
<string>Last stop at 6m</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<item row="2" column="2">
|
||||
<widget class="QSpinBox" name="gfhigh">
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
|
@ -329,14 +289,14 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="drop_stone_mode">
|
||||
<item row="4" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="lastStop">
|
||||
<property name="text">
|
||||
<string>Drop to first depth</string>
|
||||
<string>Last stop at 6m</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="rebreathermode">
|
||||
<property name="currentText">
|
||||
<string/>
|
||||
|
@ -346,6 +306,53 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QSpinBox" name="gflow">
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>150</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="drop_stone_mode">
|
||||
<property name="text">
|
||||
<string>Drop to first depth</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>GF low</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="recreational_mode">
|
||||
<property name="text">
|
||||
<string>Recreational mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -169,7 +169,7 @@ void DiveProfileItem::modelDataChanged(const QModelIndex &topLeft, const QModelI
|
|||
for (int i = 0; i < dataModel->rowCount(); i++, entry++) {
|
||||
int max = maxCeiling(i);
|
||||
// Don't scream if we violate the ceiling by a few cm
|
||||
if (entry->depth < max - 100) {
|
||||
if (entry->depth < max - 100 && entry->sec > 0) {
|
||||
profileColor = QColor(Qt::red);
|
||||
if (!eventAdded) {
|
||||
add_event(&displayed_dive.dc, entry->sec, SAMPLE_EVENT_CEILING, -1, max / 1000, "planned waypoint above ceiling");
|
||||
|
|
|
@ -50,6 +50,7 @@ struct preferences default_prefs = {
|
|||
.display_runtime = true,
|
||||
.display_duration = true,
|
||||
.display_transitions = true,
|
||||
.recreational_mode = false,
|
||||
.bottomsac = 20000,
|
||||
.decosac = 17000,
|
||||
.o2consumption = 720,
|
||||
|
|
Loading…
Reference in a new issue