aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/opendeco.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/opendeco.c')
-rw-r--r--src/opendeco.c208
1 files changed, 208 insertions, 0 deletions
diff --git a/src/opendeco.c b/src/opendeco.c
new file mode 100644
index 0000000..9231ab3
--- /dev/null
+++ b/src/opendeco.c
@@ -0,0 +1,208 @@
+/* SPDX-License-Identifier: MIT-0 */
+
+#include <argp.h>
+#include <locale.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "deco.h"
+#include "schedule.h"
+#include "output.h"
+
+#define MOD_OXY (abs_depth(msw_to_bar(6)))
+
+#ifndef VERSION
+#define VERSION "unknown version"
+#endif
+
+/* argp settings */
+static char args_doc[] = "";
+static char doc[] = "Implementation of Buhlmann ZH-L16 with Gradient Factors:"
+ "\vExamples:\n\n"
+ "\t./opendeco -d 18 -t 60 -g Air\n"
+ "\t./opendeco -d 30 -t 60 -g EAN32\n"
+ "\t./opendeco -d 40 -t 120 -g 21/35 -l 20 -h 80 --decogasses Oxygen,EAN50\n";
+const char *argp_program_bug_address = "<~tsegers/opendeco@lists.sr.ht> or https://todo.sr.ht/~tsegers/opendeco";
+const char *argp_program_version = "opendeco " VERSION;
+
+static struct argp_option options[] = {
+ {"depth", 'd', "NUMBER", 0, "Set the depth of the dive in meters", 0},
+ {"time", 't', "NUMBER", 0, "Set the time of the dive in minutes", 1},
+ {"gas", 'g', "STRING", 0, "Set the bottom gas used during the dive, defaults to Air", 2},
+ {"gflow", 'l', "NUMBER", 0, "Set the gradient factor at the first stop, defaults to 30", 3},
+ {"gfhigh", 'h', "NUMBER", 0, "Set the gradient factor at the surface, defaults to 75", 4},
+ {"decogasses", 'G', "LIST", 0, "Set the gasses available for deco", 5},
+ {0, 0, 0, 0, 0, 0}
+};
+
+struct arguments {
+ double depth;
+ double time;
+ char *gas;
+ int gflow;
+ int gfhigh;
+ char *decogasses;
+};
+
+static error_t parse_opt(int key, char *arg, struct argp_state *state)
+{
+ struct arguments *arguments = state->input;
+
+ switch (key) {
+ case 'd':
+ arguments->depth = arg ? atof(arg) : -1;
+ break;
+ case 't':
+ arguments->time = arg ? atof(arg) : -1;
+ break;
+ case 'g':
+ arguments->gas = arg;
+ break;
+ case 'G':
+ arguments->decogasses = arg;
+ break;
+ case 'l':
+ arguments->gflow = arg ? atoi(arg) : 100;
+ break;
+ case 'h':
+ arguments->gfhigh = arg ? atoi(arg) : 100;
+ break;
+ case ARGP_KEY_END:
+ if (arguments->depth < 0 || arguments->time < 0) {
+ argp_state_help(state, stderr, ARGP_HELP_USAGE);
+ argp_failure(state, 1, 0, "Options -d and -t are required. See --help for more information");
+ exit(ARGP_ERR_UNKNOWN);
+ }
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0};
+
+void print_segment_callback(const decostate_t *ds, const waypoint_t wp, segtype_t type)
+{
+ static double last_depth;
+ static double runtime;
+
+ wchar_t sign;
+
+ runtime += wp.time;
+
+ if (wp.depth < last_depth)
+ sign = ASC;
+ else if (wp.depth > last_depth)
+ sign = DEC;
+ else
+ sign = LVL;
+
+ if (type != SEG_TRAVEL)
+ print_planline(sign, wp.depth, wp.time, runtime, wp.gas);
+
+ last_depth = wp.depth;
+}
+
+int parse_gasses(gas_t **gasses, char *str)
+{
+ if (!str) {
+ *gasses = NULL;
+ return 0;
+ }
+
+ /* count number of gasses in string */
+ int nof_gasses = 1;
+
+ for (int c = 0; str[c]; c++)
+ if (str[c] == ',')
+ nof_gasses++;
+
+ /* allocate gas array */
+ gas_t *deco_gasses = malloc(nof_gasses * sizeof(gas_t));
+
+ /* fill gas array */
+ char *gas_str = NULL;
+ int gas_idx = 0;
+
+ while (1) {
+ if (!gas_str)
+ gas_str = strtok(str, ",");
+ else
+ gas_str = strtok(NULL, ",");
+
+ if (!gas_str)
+ break;
+
+ scan_gas(&deco_gasses[gas_idx], gas_str);
+ gas_idx++;
+ }
+
+ *gasses = deco_gasses;
+ return nof_gasses;
+}
+
+int main(int argc, char *argv[])
+{
+ setlocale(LC_ALL, "en_US.utf8");
+
+ /* argp */
+ struct arguments arguments;
+
+ arguments.depth = -1;
+ arguments.time = -1;
+ arguments.gas = "Air";
+ arguments.gflow = 30;
+ arguments.gfhigh = 75;
+ arguments.decogasses = "";
+
+ argp_parse(&argp, argc, argv, 0, 0, &arguments);
+
+ /* setup */
+ decostate_t ds;
+ init_decostate(&ds, arguments.gflow, arguments.gfhigh, msw_to_bar(3));
+ double dec_per_min = msw_to_bar(9);
+
+ gas_t bottom_gas;
+ scan_gas(&bottom_gas, arguments.gas);
+
+ gas_t *deco_gasses;
+ int nof_gasses = parse_gasses(&deco_gasses, arguments.decogasses);
+
+ /* override oxygen mod */
+ for (int i = 0; i < nof_gasses; i++)
+ if (gas_o2(&deco_gasses[i]) == 100)
+ deco_gasses[i].mod = MOD_OXY;
+
+ /* simulate dive */
+ double descent_time = msw_to_bar(arguments.depth) / dec_per_min;
+ double bottom_time = max(1, arguments.time - descent_time);
+
+ waypoint_t waypoints[] = {
+ {.depth = abs_depth(msw_to_bar(arguments.depth)), .time = descent_time, &bottom_gas},
+ {.depth = abs_depth(msw_to_bar(arguments.depth)), .time = bottom_time, &bottom_gas},
+ };
+
+ print_planhead();
+ simulate_dive(&ds, waypoints, len(waypoints), &print_segment_callback);
+
+ /* generate deco schedule */
+ double depth = waypoints[len(waypoints) - 1].depth;
+ const gas_t *gas = waypoints[len(waypoints) - 1].gas;
+
+ /* determine @+5 TTS */
+ decostate_t ds_ = ds;
+ add_segment_const(&ds_, depth, 5, gas);
+ decoinfo_t di_plus5 = calc_deco(&ds_, depth, gas, deco_gasses, nof_gasses, NULL);
+
+ /* print actual deco schedule */
+ decoinfo_t di = calc_deco(&ds, depth, gas, deco_gasses, nof_gasses, &print_segment_callback);
+
+ /* output deco info and disclaimer */
+ wprintf(L"\nNDL: %i TTS: %i TTS @+5: %i\n", (int) floor(di.ndl), (int) ceil(di.tts), (int) ceil(di_plus5.tts));
+ print_planfoot(&ds);
+
+ return 0;
+}