Mercurial > public > ostc4
diff Discovery/Src/simulation.c @ 38:5f11787b4f42
include in ostc4 repository
author | heinrichsweikamp |
---|---|
date | Sat, 28 Apr 2018 11:52:34 +0200 |
parents | |
children | 8f8ea3a32e82 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Discovery/Src/simulation.c Sat Apr 28 11:52:34 2018 +0200 @@ -0,0 +1,913 @@ +/////////////////////////////////////////////////////////////////////////////// +/// -*- coding: UTF-8 -*- +/// +/// \file Discovery/Src/simulation.c +/// \brief Contains dive simulation functionality +/// \author Heinrichs Weikamp gmbh +/// \date 13-Oct-2014 +/// +/// \details +/// The simulation uses "extern SDiveState stateSim" defined in dataCentral.h" +/// +/// simulation_start(void) sets stateUsed to stateSim and initializes simulation +/// simulation_UpdateLifeData should be called at least once per second +/// simulation_end() sets stateUsed back to stateReal +/// +/// $Id$ +/////////////////////////////////////////////////////////////////////////////// +/// \par Copyright (c) 2014-2018 Heinrichs Weikamp gmbh +/// +/// This program is free software: you can redistribute it and/or modify +/// it under the terms of the GNU General Public License as published by +/// the Free Software Foundation, either version 3 of the License, or +/// (at your option) any later version. +/// +/// This program is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with this program. If not, see <http://www.gnu.org/licenses/>. +////////////////////////////////////////////////////////////////////////////// + +#include <string.h> +#include "simulation.h" + +#include "decom.h" +#include "calc_crush.h" +#include "data_exchange.h" +#include "timer.h" +#include "check_warning.h" +#include "vpm.h" +#include "buehlmann.h" +#include "logbook_miniLive.h" + +//Private state variables +float sim_aim_depth_meter; +_Bool sim_head_decostops = 1; + +const float sim_descent_rate_meter_per_min = 20; + + +//Private functions +float sim_get_ambiant_pressure(SDiveState * pDiveState); +void sim_reduce_deco_time_one_second(SDiveState* pDiveState); + +/** + ****************************************************************************** + * @brief sets heed_decostops_while_ascending + ****************************************************************************** + * @param heed_decostops_while_ascending : true -> deco_stops are considered while ascending + * @return void + */ +void simulation_set_heed_decostops(_Bool heed_decostops_while_ascending) +{ + sim_head_decostops = heed_decostops_while_ascending; +} + +/** + ****************************************************************************** + * @brief start of simulation + ****************************************************************************** + * @return void + */ +void simulation_start(int aim_depth) +{ + copyDiveSettingsToSim(); + copyVpmRepetetiveDataToSim(); + //vpm_init(&stateSimGetPointerWrite()->vpm, stateSimGetPointerWrite()->diveSettings.vpm_conservatism, 0, 0); + stateSimGetPointerWrite()->lifeData.counterSecondsShallowDepth = 0; + stateSimGetPointerWrite()->mode = MODE_DIVE; + if(aim_depth <= 0) + aim_depth = 20; + simulation_set_aim_depth(aim_depth); + timer_init(); + stateUsed = &stateSim; + stateSim.lifeData.boolResetAverageDepth = 1; + decoLock = DECO_CALC_init_as_is_start_of_dive; + + stateSim.lifeData.apnea_total_max_depth_meter = 0; +} + +/** + ****************************************************************************** + * @brief end of simulation + ****************************************************************************** + * + * @return void + */ +void simulation_exit(void) +{ + timer_Stopwatch_Stop(); + set_stateUsedToReal(); +} + +/** + ****************************************************************************** + * @brief simulates change of Lifedata (saturation, depth change, etc.) within one second + ****************************************************************************** + * + * @param checkOncePerSecond : true -> simulation in real time (function is evaluated only once per second) + * and copy of parts of LifeData from SmallCPU with each call from HAL_TIM_PeriodElapsedCallback() + * : false -> fast simulation (many simulation cycles per second are possible) + * @return void + */ +void simulation_UpdateLifeData( _Bool checkOncePerSecond) +{ + SDiveState * pDiveState = &stateSim; + + static int last_second = -1; + static _Bool two_second = 0; + static float lastPressure_bar = 0; + + if(checkOncePerSecond) + { + pDiveState->lifeData.temperature_celsius = stateRealGetPointer()->lifeData.temperature_celsius; + pDiveState->lifeData.compass_heading = stateRealGetPointer()->lifeData.compass_heading; + pDiveState->lifeData.battery_charge = stateRealGetPointer()->lifeData.battery_charge; + + int now = current_second(); + if( last_second == now) + return; + last_second = now; + + if(!two_second) + two_second = 1; + else + { + two_second = 0; + if(lastPressure_bar >= 0) + { + //2 seconds * 30 == 1 minute, bar * 10 = meter + pDiveState->lifeData.ascent_rate_meter_per_min = (lastPressure_bar - pDiveState->lifeData.pressure_ambient_bar) * 30 * 10; + } + lastPressure_bar = pDiveState->lifeData.pressure_ambient_bar; + } + } + else if(pDiveState->lifeData.depth_meter <= (float)(decom_get_actual_deco_stop(pDiveState) + 0.001)) + sim_reduce_deco_time_one_second(pDiveState); + + if(getLicence() == LICENCEBONEX) + { + pDiveState->lifeData.scooterType = stateRealGetPointer()->lifeData.scooterType; + pDiveState->lifeData.scooterTemperature = stateRealGetPointer()->lifeData.scooterTemperature; + pDiveState->lifeData.scooterAgeInMilliSeconds = stateRealGetPointer()->lifeData.scooterAgeInMilliSeconds; + pDiveState->lifeData.scooterDrehzahl = stateRealGetPointer()->lifeData.scooterDrehzahl; + pDiveState->lifeData.scooterRestkapazitaet = stateRealGetPointer()->lifeData.scooterRestkapazitaet; + pDiveState->lifeData.scooterWattstunden = stateRealGetPointer()->lifeData.scooterWattstunden; + pDiveState->lifeData.scooterAmpere = stateRealGetPointer()->lifeData.scooterAmpere; + pDiveState->lifeData.scooterSpannung = stateRealGetPointer()->lifeData.scooterSpannung; + pDiveState->lifeData.scooterSpeed = stateRealGetPointer()->lifeData.scooterSpeed; + pDiveState->lifeData.scooterRestkapazitaetWhBased = stateRealGetPointer()->lifeData.scooterRestkapazitaetWhBased; + pDiveState->lifeData.scooterRestkapazitaetVoltageBased = stateRealGetPointer()->lifeData.scooterRestkapazitaetVoltageBased; + } + + pDiveState->lifeData.ppO2Sensor_bar[0] = stateRealGetPointer()->lifeData.ppO2Sensor_bar[0]; + pDiveState->lifeData.ppO2Sensor_bar[1] = stateRealGetPointer()->lifeData.ppO2Sensor_bar[1]; + pDiveState->lifeData.ppO2Sensor_bar[2] = stateRealGetPointer()->lifeData.ppO2Sensor_bar[2]; + pDiveState->lifeData.sensorVoltage_mV[0] = stateRealGetPointer()->lifeData.sensorVoltage_mV[0]; + pDiveState->lifeData.sensorVoltage_mV[1] = stateRealGetPointer()->lifeData.sensorVoltage_mV[1]; + pDiveState->lifeData.sensorVoltage_mV[2] = stateRealGetPointer()->lifeData.sensorVoltage_mV[2]; + + pDiveState->lifeData.dive_time_seconds += 1; + pDiveState->lifeData.pressure_ambient_bar = sim_get_ambiant_pressure(pDiveState); + + if(!is_ambient_pressure_close_to_surface(&pDiveState->lifeData) && !(stateSimGetPointer()->lifeData.counterSecondsShallowDepth) ) + { + pDiveState->lifeData.dive_time_seconds_without_surface_time += 1; + } + + if(is_ambient_pressure_close_to_surface(&pDiveState->lifeData)) // new hw 170214 + { + if(!(stateSimGetPointer()->lifeData.counterSecondsShallowDepth)) + { + if(pDiveState->diveSettings.diveMode != DIVEMODE_Apnea) + pDiveState->lifeData.counterSecondsShallowDepth = settingsGetPointer()->timeoutDiveReachedZeroDepth - 15; + else + { + pDiveState->lifeData.apnea_last_dive_time_seconds = pDiveState->lifeData.dive_time_seconds; + if(pDiveState->lifeData.apnea_last_dive_time_seconds > pDiveState->lifeData.dive_time_seconds_without_surface_time) + pDiveState->lifeData.apnea_last_dive_time_seconds = pDiveState->lifeData.dive_time_seconds_without_surface_time; + pDiveState->lifeData.apnea_last_max_depth_meter = pDiveState->lifeData.max_depth_meter; + pDiveState->lifeData.counterSecondsShallowDepth = 1; + } + } + } + else + { + pDiveState->lifeData.counterSecondsShallowDepth = 0; + } + + pDiveState->lifeData.depth_meter = (pDiveState->lifeData.pressure_ambient_bar - pDiveState->lifeData.pressure_surface_bar) * 10.0f; + if(pDiveState->lifeData.max_depth_meter < pDiveState->lifeData.depth_meter) + pDiveState->lifeData.max_depth_meter = pDiveState->lifeData.depth_meter; + + /* apnoe specials + */ + if(pDiveState->diveSettings.diveMode == DIVEMODE_Apnea) + { + if(pDiveState->lifeData.max_depth_meter > pDiveState->lifeData.apnea_total_max_depth_meter) + pDiveState->lifeData.apnea_total_max_depth_meter = pDiveState->lifeData.max_depth_meter; + + if(pDiveState->lifeData.counterSecondsShallowDepth) + { + pDiveState->lifeData.dive_time_seconds = 0; + pDiveState->lifeData.max_depth_meter = 0; + pDiveState->lifeData.boolResetAverageDepth = 1; + pDiveState->lifeData.boolResetStopwatch = 1; + } + } + + /* average depth + */ + float *AvgDepthValue = &pDiveState->lifeData.average_depth_meter; + float DepthNow = pDiveState->lifeData.depth_meter; + uint32_t *AvgDepthCount = &pDiveState->lifeData.internal.average_depth_meter_Count; + uint32_t *AvgDepthTimer = &pDiveState->lifeData.internal.average_depth_last_update_dive_time_seconds_without_surface_time; + uint32_t AvgSecondsSinceLast; + uint32_t DiveTime = pDiveState->lifeData.dive_time_seconds_without_surface_time; + + if(pDiveState->lifeData.boolResetAverageDepth) + { + *AvgDepthValue = DepthNow; + *AvgDepthCount = 1; + *AvgDepthTimer = DiveTime; + pDiveState->lifeData.boolResetAverageDepth = 0; + } + else if (DiveTime > *AvgDepthTimer) + { + AvgSecondsSinceLast = DiveTime - *AvgDepthTimer; + for(int i=0;i<AvgSecondsSinceLast;i++) + { + *AvgDepthValue = (*AvgDepthValue * *AvgDepthCount + DepthNow) / (*AvgDepthCount + 1); + *AvgDepthCount += 1; + } + *AvgDepthTimer = DiveTime; + } + if(*AvgDepthCount == 0) + *AvgDepthValue = 0; + + /* Exposure Tissues + */ + decom_tissues_exposure(1, &pDiveState->lifeData); + /* moved to updateSetpointStateUsed() + pDiveState->lifeData.ppO2 = decom_calc_ppO2( pDiveState->lifeData.pressure_ambient_bar, &pDiveState->lifeData.actualGas); + */ + decom_oxygen_calculate_cns_exposure(1, &pDiveState->lifeData.actualGas, pDiveState->lifeData.pressure_ambient_bar, &pDiveState->lifeData.cns); + //if((pDiveState->lifeData.depth_meter < 0.1f) || (pDiveState->lifeData.dive_time_seconds > 1*60*60)) +// if(pDiveState->lifeData.dive_time_seconds > 1*60*60) + if(pDiveState->lifeData.dive_time_seconds > 5*60*60) // test Dirk Berben + { + simulation_exit(); + } + if(stateSimGetPointer()->lifeData.counterSecondsShallowDepth) + { + stateSimGetPointerWrite()->lifeData.counterSecondsShallowDepth += 1; + if(stateSimGetPointer()->lifeData.counterSecondsShallowDepth >= settingsGetPointer()->timeoutDiveReachedZeroDepth) + simulation_exit(); + } + vpm_crush(pDiveState); +} + +/** + ****************************************************************************** + * @brief adds extra time for fast simulation + ****************************************************************************** + *@param minutes + * @return float : new pressure + */ +void simulation_add_time(int minutes) +{ + for(int i = 0; i < 60 * minutes; i++) + { + simulation_UpdateLifeData(0); + updateMiniLiveLogbook(0); + timer_UpdateSecond(0); + } +} + +/** + ****************************************************************************** + * @brief get aim_depth + ****************************************************************************** + * @return sim_aim_depth_meter; + */ + +uint16_t simulation_get_aim_depth(void) +{ + return (uint16_t)sim_aim_depth_meter; +} + +/** + ****************************************************************************** + * @brief get heed decostops + ****************************************************************************** + * @return true if ascend follows decostops; + */ + +_Bool simulation_get_heed_decostops(void) +{ + return sim_head_decostops; +} + +/** + ****************************************************************************** + * @brief sets aim_depth + ****************************************************************************** + *@param depth_meter + * @return float : new pressure + */ +void simulation_set_aim_depth(int depth_meter) +{ + sim_aim_depth_meter = depth_meter; +} + +/** + ****************************************************************************** + * @brief simulates ambiant pressure depending on aim depth + ****************************************************************************** + * @note if aim_depth != actual depth, the depth change within one second + * (depending on descent or ascent) rate is calculated + * @param SDiveState* pDiveState: + * @return float : new ambiant pressure + */ +float sim_get_ambiant_pressure(SDiveState * pDiveState) +{ + //Calc next depth + uint8_t actual_deco_stop = decom_get_actual_deco_stop(pDiveState); + float depth_meter = pDiveState->lifeData.depth_meter; + float surface_pressure_bar = pDiveState->lifeData.pressure_surface_bar; + if(depth_meter < sim_aim_depth_meter) + { + depth_meter = depth_meter + sim_descent_rate_meter_per_min / 60; + if(depth_meter > sim_aim_depth_meter) + depth_meter = sim_aim_depth_meter; + } + else if(depth_meter > sim_aim_depth_meter) + { + + depth_meter -= pDiveState->diveSettings.ascentRate_meterperminute / 60; + if(depth_meter < sim_aim_depth_meter) + depth_meter = sim_aim_depth_meter; + + if(sim_head_decostops && depth_meter < actual_deco_stop) + { + if(actual_deco_stop < (depth_meter + pDiveState->diveSettings.ascentRate_meterperminute / 60)) + depth_meter = actual_deco_stop; + else + depth_meter += pDiveState->diveSettings.ascentRate_meterperminute / 60; + } + + } + + return surface_pressure_bar + depth_meter / 10; +} + + +/** + ****************************************************************************** + * @brief Reduces deco time of deepest stop by one second + ****************************************************************************** + * @note called during fast simulation + * @param SDiveState* pDiveState: + * @return void + */ +void sim_reduce_deco_time_one_second(SDiveState* pDiveState) +{ + SDecoinfo* pDecoinfo; + if(pDiveState->diveSettings.deco_type.ub.standard == GF_MODE) + pDecoinfo = &pDiveState->decolistBuehlmann; + else + pDecoinfo = &pDiveState->decolistVPM; + + //Reduce deco time of deepest stop by one second + for(int i = DECOINFO_STRUCT_MAX_STOPS -1 ;i >= 0; i--) + { + if(pDecoinfo->output_stop_length_seconds[i] > 0) + { + pDecoinfo->output_stop_length_seconds[i]--; + break; + } + } +} + +SDecoinfo* simulation_decoplaner(uint16_t depth_meter, uint16_t intervall_time_minutes, uint16_t dive_time_minutes, uint8_t *gasChangeListDepthGas20x2) +{ + uint8_t ptrGasChangeList = 0; // new hw 160704 + + SDiveState * pDiveState = &stateSim; + copyDiveSettingsToSim(); + vpm_init(&pDiveState->vpm, pDiveState->diveSettings.vpm_conservatism, 0, 0); + //buehlmann_init(); + //timer_init(); + memset(&pDiveState->events,0, sizeof(SEvents)); + pDiveState->diveSettings.internal__pressure_first_stop_ambient_bar_as_upper_limit_for_gf_low_otherwise_zero = 0; + //Calc desaturation during intervall (with Air) + setActualGasAir(&pDiveState->lifeData); + if(intervall_time_minutes > 0) + { + decom_tissues_exposure(intervall_time_minutes * 60, &pDiveState->lifeData); + decom_oxygen_calculate_cns_degrade(&pDiveState->lifeData.cns, intervall_time_minutes * 60); + } + + //Switch to first Gas + setActualGasFirst(&pDiveState->lifeData); + + // new hw 160704 + if(gasChangeListDepthGas20x2) + { + gasChangeListDepthGas20x2[ptrGasChangeList++] = 0; + gasChangeListDepthGas20x2[ptrGasChangeList++] = pDiveState->lifeData.actualGas.GasIdInSettings; + gasChangeListDepthGas20x2[0] =0; // depth zero + } + + //Going down / descent + simulation_set_aim_depth(depth_meter); + for(int i = 0; i < 60 * dive_time_minutes; i++) + { + simulation_UpdateLifeData(0); + check_warning2(pDiveState); + if(pDiveState->warnings.betterGas) + { + setActualGas(&pDiveState->lifeData,actualBetterGasId(),pDiveState->lifeData.actualGas.setPoint_cbar); + if(gasChangeListDepthGas20x2 && (pDiveState->diveSettings.diveMode == DIVEMODE_OC)) + { + gasChangeListDepthGas20x2[ptrGasChangeList++] = pDiveState->lifeData.depth_meter; + gasChangeListDepthGas20x2[ptrGasChangeList++] = actualBetterGasId(); + } + } + } + + decom_CreateGasChangeList(&pDiveState->diveSettings, &pDiveState->lifeData); // was there before and needed for buehlmann_calc_deco and vpm_calc + + // new hw 160704 + if(gasChangeListDepthGas20x2 && (pDiveState->diveSettings.diveMode == DIVEMODE_OC)) + { + // change direction from better gas to deco gas + gasChangeListDepthGas20x2[ptrGasChangeList++] = 255; + gasChangeListDepthGas20x2[ptrGasChangeList++] = 255; + + // ascend (deco) gases + for(int i=1; i<=5;i++) + { + if(pDiveState->diveSettings.decogaslist[i].change_during_ascent_depth_meter_otherwise_zero == 0) + break; + gasChangeListDepthGas20x2[ptrGasChangeList++] = pDiveState->diveSettings.decogaslist[i].change_during_ascent_depth_meter_otherwise_zero; + gasChangeListDepthGas20x2[ptrGasChangeList++] = pDiveState->diveSettings.decogaslist[i].GasIdInSettings; + } + gasChangeListDepthGas20x2[0] = 0; + } + + // deco and ascend calc + if(pDiveState->diveSettings.deco_type.ub.standard == GF_MODE) + { + /* this does modify the cns now 11.06.2015 */ + buehlmann_calc_deco(&pDiveState->lifeData,&pDiveState->diveSettings,&pDiveState->decolistBuehlmann); + pDiveState->lifeData.cns += buehlmann_get_gCNS(); + return &pDiveState->decolistBuehlmann; + } + else + { + /* this does modify the cns now 11.06.2015 */ + vpm_calc(&pDiveState->lifeData,&pDiveState->diveSettings,&pDiveState->vpm,&pDiveState->decolistVPM, DECOSTOPS); + pDiveState->lifeData.cns += vpm_get_CNS(); + return &pDiveState->decolistVPM; + } +} + +float sGChelper_bar(uint16_t depth_meter) +{ + SDiveState * pDiveState = &stateSim; + float ambient, surface, density, meter; + + surface = pDiveState->lifeData.pressure_surface_bar; + + if(!depth_meter) + return surface; + + density = ((float)( 100 + settingsGetPointer()->salinity)) / 100.0f; + meter = depth_meter * (0.09807f * density); + ambient = (meter + surface); + + return ambient; +} + + +/** + ****************************************************************************** + * @brief simulation_helper_change_points + ****************************************************************************** + * @param + * @return void + */ +void simulation_helper_change_points(SSimDataSummary *outputSummary, uint16_t depth_meter, uint16_t dive_time_minutes, SDecoinfo *decoInfoInput, const uint8_t *gasChangeListDepthGas20x2) +{ + uint8_t ptrDecoInfo = 0; + uint16_t actualDepthPoint = 0; + uint16_t nextDepthPoint = 0; + uint8_t actualConsumGasId = 0; + uint8_t nextGasChangeMeter = 0; + uint8_t ptrChangeList = 0; + + float timeThis = 0; + float timeSummary = 0; + float sim_descent_rate_meter_per_min_local = 10; + float sim_ascent_rate_meter_per_min_local = 10; + + SDiveState * pDiveState = &stateSim; + + uint8_t depthDecoNext, depthLast, depthSecond, depthInc; + + if(pDiveState->diveSettings.deco_type.ub.standard == GF_MODE) + { + sim_descent_rate_meter_per_min_local = sim_descent_rate_meter_per_min; // const float + sim_ascent_rate_meter_per_min_local = pDiveState->diveSettings.ascentRate_meterperminute; + } + else + { + sim_descent_rate_meter_per_min_local = sim_descent_rate_meter_per_min; // const float + sim_ascent_rate_meter_per_min_local = 10;// fix in vpm_calc_deco(); + } + + outputSummary->descentRateMeterPerMinute = sim_descent_rate_meter_per_min_local; + outputSummary->ascentRateMeterPerMinute = sim_ascent_rate_meter_per_min_local; + + // bottom gas ppO2 + if(gasChangeListDepthGas20x2) + { + nextGasChangeMeter = gasChangeListDepthGas20x2[ptrChangeList++]; + actualConsumGasId = gasChangeListDepthGas20x2[ptrChangeList++]; + nextGasChangeMeter = gasChangeListDepthGas20x2[ptrChangeList++]; + + while(actualDepthPoint < depth_meter) + { + if(nextGasChangeMeter && (nextGasChangeMeter < depth_meter) && (gasChangeListDepthGas20x2[ptrChangeList] != 255)) // list has 255,255 for turn from travel to deco + { + nextDepthPoint = nextGasChangeMeter; + } + else + { + nextDepthPoint = depth_meter; + } + + if(actualConsumGasId > 5) // safety first + actualConsumGasId = 0; + + actualDepthPoint = nextDepthPoint; + + if(actualDepthPoint != depth_meter) + { + actualConsumGasId = gasChangeListDepthGas20x2[ptrChangeList++]; + nextGasChangeMeter = gasChangeListDepthGas20x2[ptrChangeList++]; + } + } + } + else + { + actualConsumGasId = pDiveState->lifeData.actualGas.GasIdInSettings; + nextGasChangeMeter = 0; + } + outputSummary->ppO2AtBottom = (sGChelper_bar(depth_meter) - WATER_VAPOUR_PRESSURE) * pDiveState->diveSettings.gas[actualConsumGasId].oxygen_percentage / 100.0f; + + + // going down + actualDepthPoint = 0; + nextDepthPoint = depth_meter; + + timeThis = ((float)(nextDepthPoint - actualDepthPoint)) / sim_descent_rate_meter_per_min_local; + timeSummary += timeThis; + outputSummary->timeToBottom = (uint16_t)timeThis; + + // bottom time + timeThis = ((float)dive_time_minutes) - timeSummary; + timeSummary += timeThis; + outputSummary->timeAtBottom = (uint16_t)timeSummary; + + + // ascend to first deco stop + actualDepthPoint = depth_meter; // that is where we are + timeThis = 0; + + if(!decoInfoInput->output_stop_length_seconds[0]) // NDL dive + { + depthLast = 0; + ptrDecoInfo = 0; + depthDecoNext = 0; + } + else + { + // prepare deco stop list + depthLast = (uint8_t)(stateUsed->diveSettings.last_stop_depth_bar * 10); + depthSecond = (uint8_t)(stateUsed->diveSettings.input_second_to_last_stop_depth_bar * 10); + depthInc = (uint8_t)(stateUsed->diveSettings.input_next_stop_increment_depth_bar * 10); + + for(ptrDecoInfo=DECOINFO_STRUCT_MAX_STOPS-1; ptrDecoInfo>0; ptrDecoInfo--) + if(decoInfoInput->output_stop_length_seconds[ptrDecoInfo]) break; + + if(ptrDecoInfo == 0) + { + depthDecoNext = depthLast; + } + else + depthDecoNext = depthSecond + (( ptrDecoInfo - 1 )* depthInc); + } + + nextDepthPoint = depthDecoNext; + if(actualDepthPoint > nextDepthPoint) + { + // flip signs! It's going up + timeThis = ((float)(actualDepthPoint - nextDepthPoint)) / sim_ascent_rate_meter_per_min_local; + actualDepthPoint = nextDepthPoint; // that is where we are + } + timeSummary += timeThis; + outputSummary->timeToFirstStop = (uint16_t)timeSummary; + outputSummary->depthMeterFirstStop = actualDepthPoint; + + //ascent + nextDepthPoint = 0; + timeThis = 0; + if(actualDepthPoint > nextDepthPoint) // only if deco + { + // ascent time + timeThis = ((float)(actualDepthPoint - nextDepthPoint)) / sim_ascent_rate_meter_per_min_local; + actualDepthPoint = actualDepthPoint; // that is where we are + + // deco stop time + for(ptrDecoInfo=0;ptrDecoInfo < DECOINFO_STRUCT_MAX_STOPS; ptrDecoInfo++) + { + timeThis += decoInfoInput->output_stop_length_seconds[ptrDecoInfo] / 60; + if(!decoInfoInput->output_stop_length_seconds[ptrDecoInfo]) break; + } + } + timeSummary += timeThis; + outputSummary->timeToSurface = (uint16_t)timeSummary; + +} + + +/** + ****************************************************************************** + * @brief simulation_gas_consumption + ****************************************************************************** + * @note called by openEdit_PlanResult() in tMenuEditPlanner.c + * @note the ascend and descend time is taken from pDiveState->lifeData.ascent_rate_meter_per_min and const float sim_descent_rate_meter_per_min + * @param outputConsumptionList list from 1 to 5 for gas 1 to 5 + * @param depth_meter for descend + * @param dive_time_minutes for descend and bottom time + * @param the calculated deco list + * @param gasConsumTravelInput: how many l/min for all but deco stops + * @param gasConsumDecoInput: how many l/min for deco stops only + * @return void + */ + +void simulation_gas_consumption(uint16_t *outputConsumptionList, uint16_t depth_meter, uint16_t dive_time_minutes, SDecoinfo *decoInfoInput, uint8_t gasConsumTravelInput, uint8_t gasConsumDecoInput, const uint8_t *gasChangeListDepthGas20x2) +{ + uint8_t ptrDecoInfo = 0; + uint8_t ptrChangeList = 0; + uint8_t actualConsumGasId = 0; + uint8_t nextGasChangeMeter = 0; + uint16_t actualDepthPoint = 0; + uint16_t nextDepthPoint = 0; + uint16_t inBetweenDepthPoint = 0; + float timeThis = 0; + float consumThis = 0; + float timeSummary = 0; + float outputConsumptionTempFloat[6]; + float sim_descent_rate_meter_per_min_local = 10; + float sim_ascent_rate_meter_per_min_local = 10; + + SDiveState * pDiveState = &stateSim; + + uint8_t depthDecoNext, depthLast, depthSecond, depthInc; + + for(int i = 1; i < 6; i++) + outputConsumptionTempFloat[i] = 0; + + if(gasChangeListDepthGas20x2) + { + nextGasChangeMeter = gasChangeListDepthGas20x2[ptrChangeList++]; + actualConsumGasId = gasChangeListDepthGas20x2[ptrChangeList++]; + nextGasChangeMeter = gasChangeListDepthGas20x2[ptrChangeList++]; + } + else + { + actualConsumGasId = pDiveState->lifeData.actualGas.GasIdInSettings; + nextGasChangeMeter = 0; + } + + if(pDiveState->diveSettings.deco_type.ub.standard == GF_MODE) + { + sim_descent_rate_meter_per_min_local = sim_descent_rate_meter_per_min; // const float + sim_ascent_rate_meter_per_min_local = pDiveState->diveSettings.ascentRate_meterperminute; + } + else + { + sim_descent_rate_meter_per_min_local = sim_descent_rate_meter_per_min; // const float + sim_ascent_rate_meter_per_min_local = 10;// fix in vpm_calc_deco(); + } + +// while((nextGasChangeMeter < depth_meter) && (actualDepthPoint < depth_meter)) + while(actualDepthPoint < depth_meter) + { + if(nextGasChangeMeter && (nextGasChangeMeter < depth_meter) && (gasChangeListDepthGas20x2[ptrChangeList] != 255)) // list has 255,255 for turn from travel to deco + { + nextDepthPoint = nextGasChangeMeter; + } + else + { + nextDepthPoint = depth_meter; + } + + if(actualConsumGasId > 5) // safety first + actualConsumGasId = 0; + + timeThis = ((float)(nextDepthPoint - actualDepthPoint)) / sim_descent_rate_meter_per_min_local; + if(actualDepthPoint) // not if on surface + { + consumThis = ((float)gasConsumTravelInput) * sGChelper_bar(actualDepthPoint) * timeThis; + } + consumThis += ((float)gasConsumTravelInput) * sGChelper_bar(nextDepthPoint -actualDepthPoint) * timeThis / 2; + outputConsumptionTempFloat[actualConsumGasId] += consumThis; + timeSummary += timeThis; + + actualDepthPoint = nextDepthPoint; + + if(actualDepthPoint != depth_meter) + { + actualConsumGasId = gasChangeListDepthGas20x2[ptrChangeList++]; + nextGasChangeMeter = gasChangeListDepthGas20x2[ptrChangeList++]; + } + } + + // bottom Time + timeThis = ((float)dive_time_minutes) - timeSummary; + + if(timeThis > 0) + { + consumThis = ((float)gasConsumTravelInput) * sGChelper_bar(depth_meter) * timeThis; + outputConsumptionTempFloat[actualConsumGasId] += consumThis; + } + + // ascend with deco stops prepare + if(gasChangeListDepthGas20x2) + { + ptrChangeList++;// gasChangeListDepthGas20x2[ptrChangeList++]; // should be the 255 + nextGasChangeMeter = gasChangeListDepthGas20x2[ptrChangeList++]; + } + else + { + nextGasChangeMeter = 0; + } + + + if(!decoInfoInput->output_stop_length_seconds[0]) // NDL dive + { + depthLast = 0; + ptrDecoInfo = 0; + } + else + { + // prepare deco stop list + depthLast = (uint8_t)(stateUsed->diveSettings.last_stop_depth_bar * 10); + depthSecond = (uint8_t)(stateUsed->diveSettings.input_second_to_last_stop_depth_bar * 10); + depthInc = (uint8_t)(stateUsed->diveSettings.input_next_stop_increment_depth_bar * 10); + + for(ptrDecoInfo=DECOINFO_STRUCT_MAX_STOPS-1; ptrDecoInfo>0; ptrDecoInfo--) + if(decoInfoInput->output_stop_length_seconds[ptrDecoInfo]) break; + } + + actualDepthPoint = depth_meter; // that is where we are + + // ascend with deco stops + while(actualDepthPoint) + { + if(ptrDecoInfo == 0) + { + depthDecoNext = depthLast; + } + else + depthDecoNext = depthSecond + (( ptrDecoInfo - 1 )* depthInc); + + if(nextGasChangeMeter && (nextGasChangeMeter > depthDecoNext)) + { + nextDepthPoint = nextGasChangeMeter; + } + else + { + nextDepthPoint = depthDecoNext; + } + + if(actualConsumGasId > 5) // safety first + actualConsumGasId = 0; + + if(actualDepthPoint > nextDepthPoint) + { + // flip signs! It's going up + timeThis = ((float)(actualDepthPoint - nextDepthPoint)) / sim_ascent_rate_meter_per_min_local; + inBetweenDepthPoint = nextDepthPoint + ((actualDepthPoint - nextDepthPoint)/2); + consumThis = ((float)gasConsumDecoInput) * sGChelper_bar(inBetweenDepthPoint) * timeThis; +/* + if(nextDepthPoint) + { + consumThis = ((float)gasConsumDecoInput) * sGChelper_bar(nextDepthPoint) * timeThis; + } + else + { + consumThis = 0; + } + consumThis += ((float)gasConsumDecoInput) * sGChelper_bar(actualDepthPoint - nextDepthPoint) * timeThis / 2; +*/ + outputConsumptionTempFloat[actualConsumGasId] += consumThis; + } + + if(nextGasChangeMeter && (nextDepthPoint == nextGasChangeMeter)) + { + actualConsumGasId = gasChangeListDepthGas20x2[ptrChangeList++]; + nextGasChangeMeter = gasChangeListDepthGas20x2[ptrChangeList++]; + } + + if(actualConsumGasId > 5) // safety first + actualConsumGasId = 0; + + if(nextDepthPoint && (nextDepthPoint == depthDecoNext)) + { + if(decoInfoInput->output_stop_length_seconds[ptrDecoInfo]) + { + timeThis = ((float)(decoInfoInput->output_stop_length_seconds[ptrDecoInfo])) / 60.0f; + consumThis = ((float)gasConsumDecoInput) * sGChelper_bar(nextDepthPoint) * timeThis; + outputConsumptionTempFloat[actualConsumGasId] += consumThis; + } + if(ptrDecoInfo != 0) + { + ptrDecoInfo--; + } + else + { + depthLast = 0; + } + } + actualDepthPoint = nextDepthPoint; + } + + // copy and return + for(int i = 1; i < 6; i++) + outputConsumptionList[i] = (uint16_t)(outputConsumptionTempFloat[i]); +} + +/** + ****************************************************************************** + * @brief Simulator control during simulated dive + ****************************************************************************** + * @note called by user via tHomeDiveMenuControl() + * @param void + * @return void + */ + + +void Sim_Descend (void) +{ + stateSimGetPointerWrite()->lifeData.counterSecondsShallowDepth = 0; + if(simulation_get_aim_depth() < 200) + simulation_set_aim_depth(simulation_get_aim_depth() + 1); +} + + +void Sim_Ascend (void) +{ + if(simulation_get_aim_depth() > 0) + simulation_set_aim_depth(simulation_get_aim_depth() - 1); +} + + +void Sim_Divetime (void) +{ + simulation_add_time(5); +} + + +void Sim_Quit (void) +{ + if(stateSimGetPointer()->lifeData.counterSecondsShallowDepth) + { + simulation_exit(); + return; + } + + if(simulation_get_aim_depth() > 0) + { + simulation_set_aim_depth(0); + } + else + { + stateSimGetPointerWrite()->lifeData.depth_meter = 0; + if(stateSimGetPointer()->diveSettings.diveMode == DIVEMODE_Apnea) + { + stateSimGetPointerWrite()->lifeData.counterSecondsShallowDepth = 1; + } + else + { + stateSimGetPointerWrite()->lifeData.counterSecondsShallowDepth = settingsGetPointer()->timeoutDiveReachedZeroDepth - 15; + } + } +}