From b5dfedd376c2ac89f7c3d14a6f5db0b5ca1d994d Mon Sep 17 00:00:00 2001 From: Tim Segers Date: Thu, 29 Sep 2022 19:12:32 +0200 Subject: Initial commit --- .clang-format | 120 ++++++++++++++++++++++++ CODE_OF_CONDUCT | 1 + LICENCE | 20 ++++ Makefile | 8 ++ deco.c | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ deco.h | 68 ++++++++++++++ opendeco.c | 81 ++++++++++++++++ output.c | 106 +++++++++++++++++++++ output.h | 19 ++++ schedule.c | 227 +++++++++++++++++++++++++++++++++++++++++++++ schedule.h | 42 +++++++++ 11 files changed, 971 insertions(+) create mode 100644 .clang-format create mode 100644 CODE_OF_CONDUCT create mode 100644 LICENCE create mode 100644 Makefile create mode 100644 deco.c create mode 100644 deco.h create mode 100644 opendeco.c create mode 100644 output.c create mode 100644 output.h create mode 100644 schedule.c create mode 100644 schedule.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a273c2a --- /dev/null +++ b/.clang-format @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 11. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: Left +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 119 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Never +Cpp11BracedListStyle: true +... diff --git a/CODE_OF_CONDUCT b/CODE_OF_CONDUCT new file mode 100644 index 0000000..a94d3c0 --- /dev/null +++ b/CODE_OF_CONDUCT @@ -0,0 +1 @@ +Don't be a dickhead, or I'll block you from the repo until you stop. diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..851f256 --- /dev/null +++ b/LICENCE @@ -0,0 +1,20 @@ +MIT No Attribution Licence + +Copyright 2022 timn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +SPDX-License-Identifier: MIT-0 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6ebe7de --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +opendeco: opendeco.c deco.c deco.h schedule.c schedule.h output.c output.h + gcc -O3 -lm -Wall -Werror opendeco.c deco.c schedule.c output.c -o opendeco + +run: opendeco + ./opendeco + +clean: + rm opendeco diff --git a/deco.c b/deco.c new file mode 100644 index 0000000..66182d9 --- /dev/null +++ b/deco.c @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: MIT-0 */ + +#include +#include +#include + +#include "deco.h" + +enum ALGO ALGO_VER = ZHL_16C; + +#define PO2_MAX (1.6) +#define END_MAX (abs_depth(msw_to_bar(30))) +#define RND(x) (round((x) *10000) / 10000) + +typedef struct zhl_n2_t { + double t; + double a[3]; + double b; +} zhl_n2_t; + +typedef struct zhl_he_t { + double t; + double a; + double b; +} zhl_he_t; + +const zhl_n2_t ZHL16N[] = { + {.t = 5.0, .a = {1.1696, 1.1696, 1.1696}, .b = 0.5578}, + {.t = 8.0, .a = {1.0000, 1.0000, 1.0000}, .b = 0.6514}, + {.t = 12.5, .a = {0.8618, 0.8618, 0.8618}, .b = 0.7222}, + {.t = 18.5, .a = {0.7562, 0.7562, 0.7562}, .b = 0.7825}, + {.t = 27.0, .a = {0.6667, 0.6667, 0.6200}, .b = 0.8126}, + {.t = 38.3, .a = {0.5933, 0.5600, 0.5043}, .b = 0.8434}, + {.t = 54.3, .a = {0.5282, 0.4947, 0.4410}, .b = 0.8693}, + {.t = 77.0, .a = {0.4701, 0.4500, 0.4000}, .b = 0.8910}, + {.t = 109.0, .a = {0.4187, 0.4187, 0.3750}, .b = 0.9092}, + {.t = 146.0, .a = {0.3798, 0.3798, 0.3500}, .b = 0.9222}, + {.t = 187.0, .a = {0.3497, 0.3497, 0.3295}, .b = 0.9319}, + {.t = 239.0, .a = {0.3223, 0.3223, 0.3065}, .b = 0.9403}, + {.t = 305.0, .a = {0.2971, 0.2850, 0.2835}, .b = 0.9477}, + {.t = 390.0, .a = {0.2737, 0.2737, 0.2610}, .b = 0.9544}, + {.t = 498.0, .a = {0.2523, 0.2523, 0.2480}, .b = 0.9602}, + {.t = 635.0, .a = {0.2327, 0.2327, 0.2327}, .b = 0.9653}, +}; + +const zhl_he_t ZHL16He[] = { + {.t = 1.88, .a = 1.6189, .b = 0.4770}, + {.t = 3.02, .a = 1.3830, .b = 0.5747}, + {.t = 4.72, .a = 1.1919, .b = 0.6527}, + {.t = 6.99, .a = 1.0458, .b = 0.7223}, + {.t = 10.21, .a = 0.9220, .b = 0.7582}, + {.t = 14.48, .a = 0.8205, .b = 0.7957}, + {.t = 20.53, .a = 0.7305, .b = 0.8279}, + {.t = 29.11, .a = 0.6502, .b = 0.8553}, + {.t = 41.20, .a = 0.5950, .b = 0.8757}, + {.t = 55.19, .a = 0.5545, .b = 0.8903}, + {.t = 70.69, .a = 0.5333, .b = 0.8997}, + {.t = 90.34, .a = 0.5189, .b = 0.9073}, + {.t = 115.29, .a = 0.5181, .b = 0.9122}, + {.t = 147.42, .a = 0.5176, .b = 0.9171}, + {.t = 188.24, .a = 0.5172, .b = 0.9217}, + {.t = 240.03, .a = 0.5119, .b = 0.9267}, +}; + +double bar_to_msw(const double bar) +{ + return bar * 10; +} + +double msw_to_bar(const double msw) +{ + return msw / 10; +} + +double abs_depth(const double gd) +{ + return gd + SURFACE_PRESSURE; +} + +double gauge_depth(const double ad) +{ + return ad - SURFACE_PRESSURE; +} + +gas_t gas_new(const unsigned char o2, const unsigned char he, double mod) +{ + assert(o2 + he <= 100); + + if (mod == MOD_AUTO) { + double mod_po2 = PO2_MAX / (o2 / 100.0); + double mod_end = END_MAX / (1 - he / 100.0); + + mod = min(mod_po2, mod_end); + } + + return (gas_t){.o2 = o2, .he = he, .n2 = 100 - o2 - he, .mod = mod}; +} + +int gas_equal(const gas_t *g1, const gas_t *g2) +{ + return g1->o2 == g2->o2 && g1->he == g2->he; +} + +unsigned char gas_o2(const gas_t *gas) +{ + return gas->o2; +} + +unsigned char gas_he(const gas_t *gas) +{ + return gas->he; +} + +unsigned char gas_n2(const gas_t *gas) +{ + return gas->n2; +} + +double gas_mod(const gas_t *gas) +{ + return gas->mod; +} + +double add_segment_ascdec(decostate_t *ds, const double dstart, const double dend, const double time, const gas_t *gas) +{ + assert(time > 0); + + const double rate = (dend - dstart) / time; + + for (int i = 0; i < 16; i++) { + double pio = gas_he(gas) / 100.0 * (dstart - P_WV); + double po = ds->phe[i]; + double r = gas_he(gas) / 100.0 * rate; + double k = log(2) / ZHL16He[i].t; + double t = time; + + ds->phe[i] = pio + r * (t - 1 / k) - (pio - po - (r / k)) * exp(-k * t); + } + + for (int i = 0; i < 16; i++) { + double pio = gas_n2(gas) / 100.0 * (dstart - P_WV); + double po = ds->pn2[i]; + double r = gas_n2(gas) / 100.0 * rate; + double k = log(2) / ZHL16N[i].t; + double t = time; + + ds->pn2[i] = pio + r * (t - 1 / k) - (pio - po - (r / k)) * exp(-k * t); + } + + /* TODO add CNS */ + /* TODO add OTU */ + + if (dend > ds->max_depth) + ds->max_depth = dend; + + return time; +} + +double add_segment_const(decostate_t *ds, const double depth, const double time, const gas_t *gas) +{ + assert(time > 0); + + for (int i = 0; i < 16; i++) { + double pio = gas_he(gas) / 100.0 * (depth - P_WV); + double po = ds->phe[i]; + double k = log(2) / ZHL16He[i].t; + double t = time; + + ds->phe[i] = po + (pio - po) * (1 - exp(-k * t)); + } + + for (int i = 0; i < 16; i++) { + double pio = gas_n2(gas) / 100.0 * (depth - P_WV); + double po = ds->pn2[i]; + double k = log(2) / ZHL16N[i].t; + double t = time; + + ds->pn2[i] = po + (pio - po) * (1 - exp(-k * t)); + } + + /* TODO add CNS */ + /* TODO add OTU */ + + if (depth > ds->max_depth) + ds->max_depth = depth; + + return time; +} + +double get_gf(const decostate_t *ds, const double depth) +{ + const unsigned char lo = ds->gflo; + const unsigned char hi = ds->gfhi; + + if (ds->firststop == -1) + return lo; + + if (depth < SURFACE_PRESSURE) + return hi; + + if (depth > ds->firststop) + return lo; + + /* interpolate lo and hi between first stop and surface */ + double next_stop = depth - ds->ceil_multiple; + return hi - (hi - lo) * gauge_depth(next_stop) / gauge_depth(ds->firststop); +} + +double round_ceiling(const decostate_t *ds, const double c) +{ + assert(ds->ceil_multiple != 0); + + return abs_depth(ds->ceil_multiple * ceil(RND(gauge_depth(c) / ds->ceil_multiple))); +} + +double ceiling(const decostate_t *ds, double gf) +{ + double c = 0; + gf /= 100; + + for (int i = 0; i < 16; i++) { + /* n2 a and b values */ + double an = ZHL16N[i].a[ALGO_VER]; + double bn = ZHL16N[i].b; + + /* he a and b values */ + double ah = ZHL16He[i].a; + double bh = ZHL16He[i].b; + + /* scale n2 and he values for a and b proportional to their pressure */ + double pn2 = ds->pn2[i]; + double phe = ds->phe[i]; + + double a = ((an * pn2) + (ah * phe)) / (pn2 + phe); + double b = ((bn * pn2) + (bh * phe)) / (pn2 + phe); + + /* update ceiling */ + c = max(c, ((pn2 + phe) - (a * gf)) / (gf / b + 1 - gf)); + } + + return round_ceiling(ds, c); +} + +void init_tissues(decostate_t *ds) +{ + const double pn2 = 0.79 * (SURFACE_PRESSURE - P_WV); + const double phe = 0.00 * (SURFACE_PRESSURE - P_WV); + + for (int i = 0; i < 16; i++) + ds->pn2[i] = pn2; + + for (int i = 0; i < 16; i++) + ds->phe[i] = phe; +} + +void init_decostate(decostate_t *ds, const unsigned char gflo, const unsigned char gfhi, const double ceil_multiple) +{ + init_tissues(ds); + + ds->gflo = gflo; + ds->gfhi = gfhi; + ds->firststop = -1; + ds->ceil_multiple = ceil_multiple; +} + +double ppO2(double depth, const gas_t *gas) +{ + return gas_o2(gas) / 100.0 * depth; +} + +double end(double depth, const gas_t *gas) +{ + return (gas_o2(gas) + gas_n2(gas)) / 100.0 * depth; +} + +double ead(double depth, const gas_t *gas) +{ + return depth * gas_n2(gas) / 79.0; +} diff --git a/deco.h b/deco.h new file mode 100644 index 0000000..5860591 --- /dev/null +++ b/deco.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: MIT-0 */ + +#ifndef DECO_H +#define DECO_H + +#include + +#define max(X, Y) (((X) > (Y)) ? (X) : (Y)) +#define min(X, Y) (((X) < (Y)) ? (X) : (Y)) +#define len(X) (sizeof(X) / sizeof((X)[0])) + +#define P_WV_BUHL 0.0627 /* Buhlmann value, Rq = 1.0, least conservative */ +#define P_WV_NAVY 0.0567 /* US. Navy value, Rq = 0.9 */ +#define P_WV_SCHR 0.0493 /* Schreiner value, Rq = 0.8, most conservative */ +#define P_WV P_WV_BUHL + +#define SURFACE_PRESSURE 1.01325 +#define MOD_AUTO 0 + +enum ALGO { + ZHL_16A = 0, + ZHL_16B = 1, + ZHL_16C = 2, +}; + +typedef struct decostate_t { + double pn2[16]; + double phe[16]; + unsigned char gflo; + unsigned char gfhi; + double firststop; + double max_depth; + double ceil_multiple; +} decostate_t; + +typedef struct gas_t { + unsigned char o2; + unsigned char he; + unsigned char n2; + double mod; +} gas_t; + +double bar_to_msw(const double bar); +double msw_to_bar(const double msw); +double abs_depth(const double gd); +double gauge_depth(const double ad); + +gas_t gas_new(const unsigned char o2, const unsigned char he, double mod); +int gas_equal(const gas_t *g1, const gas_t *g2); +unsigned char gas_o2(const gas_t *gas); +unsigned char gas_he(const gas_t *gas); +unsigned char gas_n2(const gas_t *gas); +double gas_mod(const gas_t *gas); + +double add_segment_ascdec(decostate_t *ds, const double dstart, const double dend, const double time, + const gas_t *gas); +double add_segment_const(decostate_t *ds, const double depth, const double time, const gas_t *gas); +double get_gf(const decostate_t *ds, const double depth); +double ceiling(const decostate_t *ds, double gf); +double round_ceiling(const decostate_t *ds, const double c); + +void init_decostate(decostate_t *ds, const unsigned char gflo, const unsigned char gfhi, const double ceil_multiple); + +double ppO2(double depth, const gas_t *gas); +double end(double depth, const gas_t *gas); +double ead(double depth, const gas_t *gas); + +#endif /* end of include guard: DECO_H */ diff --git a/opendeco.c b/opendeco.c new file mode 100644 index 0000000..0e9880d --- /dev/null +++ b/opendeco.c @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: MIT-0 */ + +#include +#include +#include + +#include "deco.h" +#include "schedule.h" +#include "output.h" + +#define MOD_OXY (abs_depth(msw_to_bar(6))) + +void print_segment_callback(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; +} + +void empty_callback(const waypoint_t wp, segtype_t type) +{ +} + +int main(int argc, const char *argv[]) +{ + setlocale(LC_ALL, "en_US.utf8"); + + /* setup */ + decostate_t ds; + init_decostate(&ds, 30, 75, msw_to_bar(3)); + + const gas_t ean32 = gas_new(32, 0, MOD_AUTO); + + /* simulate dive */ + waypoint_t waypoints[] = { + {.depth = abs_depth(msw_to_bar(30)), .time = 3.333, &ean32}, + {.depth = abs_depth(msw_to_bar(30)), .time = 116.666, &ean32}, + }; + + 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; + + gas_t deco_gasses[] = { + /* gas_new(40, 0, MOD_AUTO), */ + gas_new(50, 0, MOD_AUTO), + /* gas_new(100, 0, MOD_OXY), */ + }; + + /* 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, len(deco_gasses), &empty_callback); + + /* print actual deco schedule */ + decoinfo_t di = calc_deco(&ds, depth, gas, deco_gasses, len(deco_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; +} diff --git a/output.c b/output.c new file mode 100644 index 0000000..3d8a597 --- /dev/null +++ b/output.c @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: MIT-0 */ + +#include +#include + +#include "output.h" + +extern enum ALGO ALGO_VER; + +void format_mm_ss(char *buf, const size_t buflen, const double time) +{ + double mm; + double ss = round(modf(time, &mm) * 60); + + /* prevents 0.99999 minutes showing as 00:60 */ + mm += ss / 60; + ss = (int) ss % 60; + + snprintf(buf, buflen, "%3i:%02i", (int) mm, (int) ss); +} + +void format_gas(char *buf, const size_t buflen, const gas_t *gas) +{ + if (gas_o2(gas) == 21 && gas_he(gas) == 0) + snprintf(buf, buflen, "Air"); + else if (gas_o2(gas) == 100) + snprintf(buf, buflen, "Oxygen"); + else if (gas_he(gas) == 0) + snprintf(buf, buflen, "Nitrox %i", gas_o2(gas)); + else + snprintf(buf, buflen, "%i/%i", gas_o2(gas), gas_he(gas)); +} + +void print_planhead() +{ + wprintf(L"DIVE PLAN\n\n"); + wprintf(L" %-1s %-5s %-8s %-7s %1s %-9s %-4s %-3s\n", "", "Depth", "Duration", "Runtime", "", "Gas", "pO2", + "EAD"); +} + +void print_planline(const wchar_t sign, const double depth, const double time, const double runtime, const gas_t *gas) +{ + static char gasbuf[11]; + static char runbuf[8]; + static char pO2buf[5]; + static char eadbuf[4]; + static char timbuf[16]; + + static gas_t last_gas; + + const int depth_m = round(bar_to_msw(gauge_depth(depth))); + const int ead_m = round(bar_to_msw(max(0, gauge_depth(ead(depth, gas))))); + + wchar_t swi = L' '; + + snprintf(runbuf, len(runbuf), "(%i)", (int) ceil(runtime)); + format_gas(gasbuf, len(gasbuf), gas); + format_mm_ss(timbuf, len(timbuf), time); + + /* print gas swich symbol if gas changed */ + if (!gas_equal(gas, &last_gas)) { + last_gas = *gas; + swi = SWI; + } + + /* only print ead and pO2 on stops */ + if (sign == LVL) { + snprintf(eadbuf, 4, "%3i", ead_m); + snprintf(pO2buf, 5, "%4.2f", ppO2(depth, gas)); + } else { + snprintf(eadbuf, 4, "%3s", "-"); + snprintf(pO2buf, 5, "%4s", "-"); + } + + wprintf(L" %lc %4im %8s %-7s %lc %-9s %s %s\n", sign, depth_m, timbuf, runbuf, swi, gasbuf, pO2buf, eadbuf); +} + +void print_planfoot(const decostate_t *ds) +{ + char *model; + char *rq; + + if (ALGO_VER == ZHL_16A) + model = "ZHL-16A"; + else if (ALGO_VER == ZHL_16B) + model = "ZHL-16B"; + else if (ALGO_VER == ZHL_16C) + model = "ZHL-16C"; + else + model = "???"; + + if (P_WV == P_WV_BUHL) + rq = "1.0"; + else if (P_WV == P_WV_NAVY) + rq = "0.9"; + else if (P_WV == P_WV_SCHR) + rq = "0.8"; + else + rq = "???"; + + wprintf(L"\nDeco model: Buhlmann %s\n", model); + wprintf(L"Conservatism: GF %i/%i, Rq = %s\n", ds->gflo, ds->gfhi, rq); + wprintf(L"Surface pressure: %4.3fbar\n\n", SURFACE_PRESSURE); + + wprintf(L"WARNING: DIVE PLAN MAY BE INACCURATE AND MAY CONTAIN\nERRORS THAT COULD LEAD TO INJURY OR DEATH.\n"); +} diff --git a/output.h b/output.h new file mode 100644 index 0000000..0cedcc7 --- /dev/null +++ b/output.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: MIT-0 */ + +#ifndef OUTPUT_H +#define OUTPUT_H + +#include + +#include "deco.h" + +#define ASC 0x2197 /* Unicode North East Arrow */ +#define LVL 0x2192 /* Unicode Rightwards Arrow */ +#define DEC 0x2198 /* Unicode South East Arrow */ +#define SWI 0x1F5D8 /* Clockwise Right and Left Semicircle Arrows */ + +void print_planhead(); +void print_planline(const wchar_t sign, const double depth, const double time, const double runtime, const gas_t *gas); +void print_planfoot(const decostate_t *ds); + +#endif /* end of include guard: OUTPUT_H */ diff --git a/schedule.c b/schedule.c new file mode 100644 index 0000000..5bfc1d5 --- /dev/null +++ b/schedule.c @@ -0,0 +1,227 @@ +/* SPDX-License-Identifier: MIT-0 */ + +#include +#include + +#include "schedule.h" + +#define SAFETY_STOP_DEPTH (abs_depth(msw_to_bar(6))) +#define SWITCH_INTERMEDIATE 1 + +const gas_t *best_gas(const double depth, const gas_t *gasses, const int nof_gasses) +{ + const gas_t *best = NULL; + double mod_best = -1; + + for (int i = 0; i < nof_gasses; i++) { + double mod = gas_mod(&gasses[i]); + + if (depth <= mod && (mod_best == -1 || mod < mod_best)) { + best = &gasses[i]; + mod_best = mod; + } + } + + return best; +} + +const gas_t *next_gas(const double depth, const gas_t *gasses, const int nof_gasses) +{ + const gas_t *next = NULL; + double mod_best = 0; + + for (int i = 0; i < nof_gasses; i++) { + double mod = gas_mod(&gasses[i]); + + if (depth > mod && mod > mod_best) { + next = &gasses[i]; + mod_best = mod; + } + } + + return next; +} + +int direct_ascent(const decostate_t *ds, const double depth, const double time, const gas_t *gas) +{ + decostate_t ds_ = *ds; + assert(ds_.firststop == -1); + + add_segment_ascdec(&ds_, depth, SURFACE_PRESSURE, time, gas); + + return ceiling(&ds_, ds_.gfhi) <= SURFACE_PRESSURE; +} + +void simulate_dive(decostate_t *ds, waypoint_t *waypoints, const int nof_waypoints, segment_callback_t seg_cb) +{ + double depth = SURFACE_PRESSURE; + double runtime = 0; + + for (int i = 0; i < nof_waypoints; i++) { + double d = waypoints[i].depth; + double t = waypoints[i].time; + const gas_t *g = waypoints[i].gas; + + if (d != depth) { + runtime += add_segment_ascdec(ds, depth, d, t, g); + depth = d; + } else { + runtime += add_segment_const(ds, d, t, g); + } + + seg_cb((waypoint_t){.depth = d, .time = t, .gas = g}, SEG_DIVE); + } +} + +double calc_ndl(decostate_t *ds, const double depth, const double ascrate, const gas_t *gas) +{ + decostate_t ds_ = *ds; + double ndl = 0; + + while (ndl < 360) { + double tmp = add_segment_const(&ds_, depth, 1, gas); + + if (!direct_ascent(&ds_, depth, gauge_depth(depth) / ascrate, gas)) + break; + + ndl += tmp; + } + + return ndl; +} + +void extend_to_ndl(decostate_t *ds, const double depth, const double ascrate, const gas_t *gas, + segment_callback_t seg_cb) +{ + double ndl = calc_ndl(ds, depth, ascrate, gas); + + /* add segment to reach ndl */ + if (ndl) { + add_segment_const(ds, depth, ndl, gas); + seg_cb((waypoint_t){.depth = depth, .time = ndl, .gas = gas}, SEG_NDL); + } + + /* either ascend directly or make a safety stop */ + if (depth < SAFETY_STOP_DEPTH || ds->max_depth < abs_depth(msw_to_bar(10))) { + /* surface */ + add_segment_ascdec(ds, depth, SURFACE_PRESSURE, gauge_depth(depth) / ascrate, gas); + seg_cb((waypoint_t){.depth = SURFACE_PRESSURE, .time = gauge_depth(depth) / ascrate, .gas = gas}, SEG_SURFACE); + } else { + /* ascend to safety stop */ + add_segment_ascdec(ds, depth, SAFETY_STOP_DEPTH, (depth - SAFETY_STOP_DEPTH) / ascrate, gas); + seg_cb((waypoint_t){.depth = SAFETY_STOP_DEPTH, .time = (depth - SAFETY_STOP_DEPTH) / ascrate, .gas = gas}, + SEG_TRAVEL); + + /* stop for 3 minutes */ + add_segment_const(ds, SAFETY_STOP_DEPTH, 3, gas); + seg_cb((waypoint_t){.depth = SAFETY_STOP_DEPTH, .time = 3, .gas = gas}, SEG_SAFETY_STOP); + + /* surface */ + add_segment_ascdec(ds, SAFETY_STOP_DEPTH, SURFACE_PRESSURE, gauge_depth(SAFETY_STOP_DEPTH) / ascrate, gas); + seg_cb((waypoint_t){.depth = SURFACE_PRESSURE, .time = gauge_depth(SAFETY_STOP_DEPTH) / ascrate, .gas = gas}, + SEG_SURFACE); + } +} + +decoinfo_t calc_deco(decostate_t *ds, const double start_depth, const gas_t *start_gas, const gas_t *deco_gasses, + const int nof_gasses, segment_callback_t seg_cb) +{ + decoinfo_t ret = {.tts = 0, .ndl = 0}; + + /* setup start parameters */ + double depth = start_depth; + const gas_t *gas = start_gas; + + const double asc_per_min = msw_to_bar(9); + + /* check if direct ascent is possible */ + if (direct_ascent(ds, depth, gauge_depth(depth) / asc_per_min, gas)) { + ret.ndl = calc_ndl(ds, depth, asc_per_min, gas); + return ret; + } + + /* determine first stop */ + double current_gf = get_gf(ds, depth); + + if (ds->firststop == -1) + ds->firststop = ceiling(ds, current_gf); + + /* switch to best deco gas if there is one available */ + const gas_t *best = best_gas(depth, deco_gasses, nof_gasses); + + if (best) + gas = best; + + /* alternate between ascending and stopping */ + for (;;) { + /* ascend */ + for (;;) { + double stopdep = ceiling(ds, current_gf); + const gas_t *next = next_gas(depth, deco_gasses, nof_gasses); + + if (SWITCH_INTERMEDIATE && next) { + /* determine switch depth */ + double switch_depth = round_ceiling(ds, next->mod) - ds->ceil_multiple; + assert(switch_depth <= next->mod); + + if (switch_depth > stopdep) { + /* ascend to switch depth */ + ret.tts += add_segment_ascdec(ds, depth, switch_depth, (depth - switch_depth) / asc_per_min, gas); + seg_cb( + (waypoint_t){.depth = switch_depth, .time = (depth - switch_depth) / asc_per_min, .gas = gas}, + SEG_TRAVEL); + + depth = switch_depth; + current_gf = get_gf(ds, depth); + + /* switch gas */ + gas = next; + + add_segment_const(ds, switch_depth, 1, gas); + seg_cb((waypoint_t){.depth = depth, .time = 1, .gas = gas}, SEG_GAS_SWITCH); + + continue; + } + } + + ret.tts += add_segment_ascdec(ds, depth, stopdep, (depth - stopdep) / asc_per_min, gas); + + if (stopdep <= SURFACE_PRESSURE) + seg_cb((waypoint_t){.depth = stopdep, .time = (depth - stopdep) / asc_per_min, .gas = gas}, + SEG_SURFACE); + else + seg_cb((waypoint_t){.depth = stopdep, .time = (depth - stopdep) / asc_per_min, .gas = gas}, + SEG_TRAVEL); + + depth = stopdep; + current_gf = get_gf(ds, depth); + + /* if the ceiling moved while we ascended, keep ascending */ + if (depth > ceiling(ds, current_gf)) + continue; + + break; + } + + /* terminate if we surfaced */ + if (depth <= SURFACE_PRESSURE) + break; + + /* switch to better gas if available */ + const gas_t *best = best_gas(depth, deco_gasses, nof_gasses); + + if (best) + gas = best; + + /* stop */ + double stoplen = 0; + + while (ceiling(ds, current_gf) == depth) + stoplen += add_segment_const(ds, depth, 1, gas); + + ret.tts += stoplen; + seg_cb((waypoint_t){.depth = depth, .time = stoplen, .gas = gas}, SEG_DECO_STOP); + } + + return ret; +} diff --git a/schedule.h b/schedule.h new file mode 100644 index 0000000..b1703d7 --- /dev/null +++ b/schedule.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: MIT-0 */ + +#ifndef SCHEDULE_H +#define SCHEDULE_H + +#include "deco.h" + +typedef struct waypoint_t { + double depth; + double time; + const gas_t *gas; +} waypoint_t; + +typedef struct decoinfo_t { + double ndl; + double tts; +} decoinfo_t; + +typedef enum segtype_t { + SEG_DECO_STOP, + SEG_DIVE, + SEG_GAS_SWITCH, + SEG_NDL, + SEG_SAFETY_STOP, + SEG_SURFACE, + SEG_TRAVEL, +} segtype_t; + +typedef void (*segment_callback_t)(const waypoint_t, const segtype_t); + +const gas_t *best_gas(const double depth, const gas_t *gasses, const int nof_gasses); +const gas_t *next_gas(const double depth, const gas_t *gasses, const int nof_gasses); + +int direct_ascent(const decostate_t *ds, const double depth, const double time, const gas_t *gas); +double calc_ndl(decostate_t *ds, const double depth, const double ascrate, const gas_t *gas); + +void simulate_dive(decostate_t *ds, waypoint_t *waypoints, const int nof_waypoints, segment_callback_t seg_cb); + +decoinfo_t calc_deco(decostate_t *ds, const double start_depth, const gas_t *start_gas, const gas_t *deco_gasses, + const int nof_gasses, segment_callback_t seg_cb); + +#endif /* end of include guard: SCHEDULE_H */ -- cgit v1.2.3-70-g09d2