Index: firmware/App/Services/Reservoirs.c =================================================================== diff -u -r9d8170a50fb293c76c5c1f1875ab3792c2b8fb50 -r9e611165fe4a2eaeccd7b2a37c8e4dfc8498b526 --- firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision 9d8170a50fb293c76c5c1f1875ab3792c2b8fb50) +++ firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision 9e611165fe4a2eaeccd7b2a37c8e4dfc8498b526) @@ -7,8 +7,8 @@ * * @file Reservoirs.c * -* @author (last) Sean Nash -* @date (last) 18-Jul-2023 +* @author (last) Dara Navaei +* @date (last) 19-Sep-2023 * * @author (original) Dara Navaei * @date (original) 21-Nov-2021 @@ -26,9 +26,15 @@ #include "NVDataMgmt.h" #include "OperationModes.h" #include "Reservoirs.h" +#include "SystemCommMessages.h" #include "TaskGeneral.h" #include "Timers.h" +/** + * @addtogroup Reservoirs + * @{ + */ + // ********** private definitions ********** #define RESERVOIR_FRESH_SETTLE_TIME_MS 15000 ///< Allocated time to settle the freshly filled reservoir in milliseconds. @@ -48,7 +54,6 @@ #define RESERVOIR_FLOW_550_MLP 550.0F ///< Reservoir flow rate 550mL/m. #define RESERVOIR_FLOW_600_MLP 600.0F ///< Reservoir flow rate 600mL/m. -#define MAX_RESERVOIR_DEPLETION_TIME_MS ( 30 * SEC_PER_MIN * MS_PER_SECOND ) ///< Maximum allowed depletion time in milliseconds. #define RESERVOIR_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Interval (ms/task time) at which the reservoir data is published on the CAN bus. #define TGT_FILL_FLOW_800_ML_PER_MIN 0.8F ///< Target fill flow rate 800 mL/min. @@ -59,6 +64,7 @@ #define DIA_FLOW_TO_FILL_FLOW_CONSTANT 2.0F ///< Dialysate flow rate to fill flow rate constant. #define MAX_RESERVOIR_VOL_BEFORE_SWITCH_ML 1900.0F ///< Maximum reservoir volume before we switch reservoirs (in mL). +#define FILL_VOLUME_INTERCEPT_ML_QD_ABOVE_400_MLPM 1100 ///< Fill volume intercept in milliliters for Qd above 400 mL/min. // ********** private data ********** @@ -87,13 +93,14 @@ static U32 lastTimeReservoirInUF; ///< Reservoir time in ultrafiltration from prior reservoir (in ms). static F32 volSpentUFML; ///< Ultrafiltration volume in milliliters. static DG_RESERVOIR_ID_T activeReservoir; ///< Active reservoir. -static F32 recirculationLevelPct; ///< Recirculation level in percent. +static OVERRIDE_F32_T recirculationLevelPct = { 0.0, 0.0, 0.0, 0 }; ///< Recirculation level in percent. static U32 reservoirSwitchStartTimeMS; ///< Reservoir switch start time in milliseconds. static S32 timeWaitToFillMS; ///< Time to wait to fill in milliseconds. static F32 targetFillFlowLPM; ///< Target fill flow in liters/minutes. static U32 previousDialysateFlowMLP; ///< Previous dialysate flow rate in mL/min. static F32 previousUFFlowMLP; ///< Previous ultrafiltration flow rate in mL/min. static DG_MIXING_RATIOS_T ratios; ///< Mixing ratios and fill prep time in milliseconds structure. +static U32 prevTargetFillVolumeML[ NUM_OF_DG_RESERVOIRS ]; ///< Previous target fill volume in milliliters. static const F32 RESERVOIR_DILUTION_RATIO = MAX_RESERVOIR_DILUTION / ( 1.0 - MAX_RESERVOIR_DILUTION ); ///< Reservoir dilution ratio. @@ -107,6 +114,7 @@ static F32 getReservoirRecirculationMaxPercent( void ); static void publishReservoirData( void ); static BOOL isDialysateTempAlarmActive( void ); +static U32 getTargetFillVolumeBasedOnDialysateFlowML( void ); static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtStartState( void ); static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtDrainState( void ); @@ -124,7 +132,7 @@ * volSpentMl, reservoirsPublicationCounter, dilutionLevelPct, dgOpMode, dgSubMode, * timeReservoirInUse, volSpentUFML, activeReservoir, recirculationLevelPct, * reservoirSwitchStartTimeMS, timeWaitToFillMS, targetFillFlowLPM, ratios - * previousDialysateFlowRate + * previousDialysateFlowRate, prevTargetFillVolumeML * @return none *************************************************************************/ void initReservoirs( void ) @@ -143,7 +151,7 @@ lastTimeReservoirInUF = 0; volSpentUFML = 0.0F; activeReservoir = getDGActiveReservoir(); - recirculationLevelPct = 0.0F; + recirculationLevelPct.data = 0.0F; reservoirSwitchStartTimeMS = 0; timeWaitToFillMS = 0; targetFillFlowLPM = 0.0F; @@ -152,6 +160,10 @@ ratios.acidMixingRatio = 0.0F; ratios.bicarbMixingRatio = 0.0F; ratios.timeFillPrepMS = 0; + + // Initialize the previous reservoir fill volume + prevTargetFillVolumeML[ DG_RESERVOIR_1 ] = FILL_RESERVOIR_TO_VOLUME_ML; + prevTargetFillVolumeML[ DG_RESERVOIR_2 ] = FILL_RESERVOIR_TO_VOLUME_ML; } /*********************************************************************//** @@ -171,8 +183,9 @@ * @brief * The execReservoirs function executes the state machine for the treatment * reservoir management during treatment mode. - * @details Inputs: reservoirsState - * @details Outputs: reservoirsState, timeReservoirInUse, volSpentML, recirculationLevelPct + * @details Inputs: reservoirsState, prevTargetFillVolumeML + * @details Outputs: reservoirsState, timeReservoirInUse, volSpentML, + * recirculationLevelPct * @return none *************************************************************************/ void execReservoirs( void ) @@ -195,7 +208,7 @@ volSpentML += ( flowRateMLPerMS * msSinceLastVolumeCalc ); timeReservoirInUse++; // Check the recirculation level - recirculationLevelPct = volSpentML / (F32)FILL_RESERVOIR_TO_VOLUME_ML; + recirculationLevelPct.data = volSpentML / (F32)prevTargetFillVolumeML[ getDGActiveReservoir() ]; } // Update the reservoir start time @@ -303,7 +316,7 @@ /*********************************************************************//** * @brief * The getFillTimeMS function calculates the fill time in milliseconds. - * @details Inputs: ratios + * @details Inputs: targetFillFlowRate, prevTargetFillVolumeML * @details Outputs: none * @return target maximum fill time *************************************************************************/ @@ -315,7 +328,7 @@ F32 totalTargetFillFlow = targetFillFlowRate + ( targetFillFlowRate * ratios.acidMixingRatio ) + ( targetFillFlowRate * ratios.bicarbMixingRatio ); // Time fill = Fill volume / Qfill. Qfill is converted from L/min to mL/min and the time is converted from minutes to milliseconds - U32 timeFillMS = ( FILL_RESERVOIR_TO_VOLUME_ML / ( ML_PER_LITER * totalTargetFillFlow ) ) * SEC_PER_MIN * MS_PER_SECOND; + U32 timeFillMS = ( getTargetFillVolumeBasedOnDialysateFlowML() / ( ML_PER_LITER * totalTargetFillFlow ) ) * SEC_PER_MIN * MS_PER_SECOND; // Add the prepare time in the DG mode fill to the calculated fill time in milliseconds U32 timeTotalFillMS = timeFillMS + ratios.timeFillPrepMS; @@ -329,27 +342,29 @@ * flow has changed the function sends the new active reservoir cycle time * to the DG. * @details Inputs: previousDialysateFlowMLP, previousUFFlowMLP - * @details Outputs: previousDialysateFlowMLP, previousUFFlowMLP, volSpentUFML, timeDepleteMS + * @details Outputs: previousDialysateFlowMLP, previousUFFlowMLP, volSpentUFML, + * timeDepleteMS, prevTargetFillVolumeML * @return none *************************************************************************/ static void calculateActiveReservoirCycleTime( void ) { + U32 activeRsrvrTgtVolML = prevTargetFillVolumeML[ getDGActiveReservoir() ]; U32 dialysateFlowMLP = getTreatmentParameterU32( TREATMENT_PARAM_DIALYSATE_FLOW ); - F32 fillTimeMS = ( (F32)FILL_RESERVOIR_TO_VOLUME_ML / ( getTargetFillFlowRateLPM() * ML_PER_LITER ) ) * SEC_PER_MIN * MS_PER_SECOND; + F32 fillTimeMS = ( (F32)activeRsrvrTgtVolML / ( getTargetFillFlowRateLPM() * ML_PER_LITER ) ) * SEC_PER_MIN * MS_PER_SECOND; F32 targetUFFlowMLP = getCurrentUFSetRate(); - F32 timeDepletionMS = ( (F32)FILL_RESERVOIR_TO_VOLUME_ML / dialysateFlowMLP ) * SEC_PER_MIN * MS_PER_SECOND; + F32 timeDepletionMS = ( (F32)activeRsrvrTgtVolML / dialysateFlowMLP ) * SEC_PER_MIN * MS_PER_SECOND; F32 timeTotalCycleMS = fillTimeMS + RESERVOIR_FRESH_SETTLE_TIME_MS + RESERVOIR_CYCLE_EXTRA_MARGIN_TIME_MS + ratios.timeFillPrepMS; F32 timeReservoirCycleMS = 0.0F; F32 timeUFDepletionMS = NEARLY_INFINITY; - F32 volFreshML = FILL_RESERVOIR_TO_VOLUME_ML - volSpentML; + F32 volFreshML = activeRsrvrTgtVolML - volSpentML; F32 timeFreshRemainingMS = ( volFreshML / (F32)dialysateFlowMLP ) * SEC_PER_MIN * MS_PER_SECOND; - F32 volMaxUFML = FILL_RESERVOIR_TO_VOLUME_ML * MAX_RESERVOIR_DILUTION; + F32 volMaxUFML = activeRsrvrTgtVolML * MAX_RESERVOIR_DILUTION; // Check if target UF flow is not zero to consider it in the calculations too if ( targetUFFlowMLP > NEARLY_ZERO ) { // If UF is not 0, the active reservoir cycle time is minimum of UF depletion and fill time - timeUFDepletionMS = ( ( (F32)FILL_RESERVOIR_TO_VOLUME_ML * RESERVOIR_DILUTION_RATIO ) / targetUFFlowMLP ) * SEC_PER_MIN * MS_PER_SECOND; + timeUFDepletionMS = ( ( (F32)activeRsrvrTgtVolML * RESERVOIR_DILUTION_RATIO ) / targetUFFlowMLP ) * SEC_PER_MIN * MS_PER_SECOND; // Calculate the ultra-filtration remaining volume // Using the ultra-filtration remaining volume and the ultra-filtration target flow rate calculate the time // The depletion time in milliseconds is the minimum time of the fresh remaining time and the depletion remaining time @@ -443,7 +458,7 @@ data.activeReservoirUFVolML = volSpentUFML; data.activeReservoirVolSpentML = volSpentML; data.dilLevelPct = dilutionLevelPct * 100; - data.recircLevelPct = recirculationLevelPct * 100; + data.recircLevelPct = getF32OverrideValue( &recirculationLevelPct ) * 100; data.timeDepletionMS = timeDepleteMS; data.timeWaitFillMS = timeWaitToFillMS; data.tempRemoveTargetFillFlow = targetFillFlowLPM; @@ -514,29 +529,33 @@ TREATMENT_RESERVOIR_MGMT_STATE_T state = TREATMENT_RESERVOIR_MGMT_WAIT_TO_FILL_STATE; // Get the target fill flow rate in L/min - F32 targetFillFlowRateLPM = getTargetFillFlowRateLPM(); + F32 targetFillFlowRateLPM = getTargetFillFlowRateLPM(); + DG_RESERVOIR_ID_T inactiveRsrvr = getDGInactiveReservoir(); + DG_RESERVOIR_ID_T activeRsrvr = getDGActiveReservoir(); // Get the ultra-filtration volume milliliters // Get the dilution level in percent = spent ultra-filtration volume / target fill volume in milliliters volSpentUFML = getReservoirUltrafiltrationVol( activeReservoir ); - dilutionLevelPct = volSpentUFML / (F32)FILL_RESERVOIR_TO_VOLUME_ML; + dilutionLevelPct = volSpentUFML / (F32)prevTargetFillVolumeML[ activeRsrvr ]; // Check if the dilution level has exceeded the limit or the spent volume is more than the amount of volume in the reservoir // If it has, trigger the fill command. This if should not happen and the predictive dilution level should trigger just in time // in advance - if ( ( dilutionLevelPct >= MAX_RESERVOIR_DILUTION ) || ( volSpentML >= (F32)FILL_RESERVOIR_TO_VOLUME_ML ) ) + if ( ( dilutionLevelPct >= MAX_RESERVOIR_DILUTION ) || ( volSpentML >= (F32)prevTargetFillVolumeML[ activeRsrvr ] ) ) { if ( DG_GEN_IDLE_MODE_STATE_FLUSH_WATER == dgSubMode ) { - cmdStartDGFill( FILL_RESERVOIR_TO_VOLUME_ML, targetFillFlowRateLPM ); + prevTargetFillVolumeML[ inactiveRsrvr ] = getTargetFillVolumeBasedOnDialysateFlowML(); + cmdStartDGFill( prevTargetFillVolumeML[ inactiveRsrvr ], targetFillFlowRateLPM ); } } // If we have active dialysate temp alarms, we want to fill immediately. else if ( TRUE == isDialysateTempAlarmActive() ) { if ( DG_GEN_IDLE_MODE_STATE_FLUSH_WATER == dgSubMode ) { - cmdStartDGFill( FILL_RESERVOIR_TO_VOLUME_ML, targetFillFlowRateLPM ); + prevTargetFillVolumeML[ inactiveRsrvr ] = getTargetFillVolumeBasedOnDialysateFlowML(); + cmdStartDGFill( prevTargetFillVolumeML[ inactiveRsrvr ], targetFillFlowRateLPM ); } } else @@ -549,7 +568,8 @@ // If the wait time has elapsed, trigger a fill command if ( timeWaitToFillMS <= 0 ) { - cmdStartDGFill( FILL_RESERVOIR_TO_VOLUME_ML, targetFillFlowRateLPM ); + prevTargetFillVolumeML[ inactiveRsrvr ] = getTargetFillVolumeBasedOnDialysateFlowML(); + cmdStartDGFill( prevTargetFillVolumeML[ inactiveRsrvr ], targetFillFlowRateLPM ); } } @@ -579,13 +599,13 @@ // If the recirculation level has exceeded the max allowed, raise the alarm to stop using the active reservoir as it has been // diluted to much - if ( recirculationLevelPct >= getReservoirRecirculationMaxPercent() ) + if ( getF32OverrideValue( &recirculationLevelPct ) >= getReservoirRecirculationMaxPercent() ) { #ifndef _RELEASE_ if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_RESERVOIRS_ALARMS ) != SW_CONFIG_ENABLE_VALUE ) #endif { - SET_ALARM_WITH_1_F32_DATA( ALARM_ID_HD_ACTIVE_RESERVOIR_RECIRCULATION_OUT_OF_RANGE, recirculationLevelPct ) + SET_ALARM_WITH_1_F32_DATA( ALARM_ID_HD_ACTIVE_RESERVOIR_RECIRCULATION_OUT_OF_RANGE, getF32OverrideValue( &recirculationLevelPct ) ) } } @@ -614,25 +634,25 @@ static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtWaitForFillSettleState( void ) { TREATMENT_RESERVOIR_MGMT_STATE_T state = TREATMENT_RESERVOIR_MGMT_WAIT_FOR_FILL_SETTLE_STATE; - DG_RESERVOIR_ID_T active = getDGActiveReservoir(); + DG_RESERVOIR_ID_T active = getDGActiveReservoir(); + DG_RESERVOIR_ID_T inactiveRsrvr = getDGInactiveReservoir(); // Get the ultra-filtration volume milliliters // Get the dilution level in percent = spent ultra-filtration volume / target fill volume in milliliters volSpentUFML = getReservoirUltrafiltrationVol( activeReservoir ); - dilutionLevelPct = volSpentUFML / (F32)FILL_RESERVOIR_TO_VOLUME_ML; + dilutionLevelPct = volSpentUFML / (F32)prevTargetFillVolumeML[ active ]; // Wait for the reservoir to settle and then send the commands to switch the active reservoir if ( ( TRUE == didTimeout( reservoirSwitchStartTimeMS, RESERVOIR_FRESH_SETTLE_TIME_MS ) ) && - ( ( dilutionLevelPct >= MAX_RESERVOIR_DILUTION ) || ( volSpentML >= (F32)FILL_RESERVOIR_TO_VOLUME_ML ) || ( getReservoirWeight( active ) > MAX_RESERVOIR_VOL_BEFORE_SWITCH_ML ) || ( TRUE == isDialysateTempAlarmActive() ) ) ) + ( ( dilutionLevelPct >= MAX_RESERVOIR_DILUTION ) || ( volSpentML >= (F32)getTargetFillVolumeBasedOnDialysateFlowML() ) || ( TRUE == isDialysateTempAlarmActive() ) ) ) { DG_SWITCH_RSRVRS_CMD_T rsrvrCmd; - DG_RESERVOIR_ID_T inactiveRes = getDGInactiveReservoir(); - rsrvrCmd.reservoirID = (U32)inactiveRes; + rsrvrCmd.reservoirID = (U32)inactiveRsrvr; rsrvrCmd.useLastTrimmerHeaterDC = TRUE; // Signal dialysis sub-mode to capture baseline volume for next reservoir. - setStartReservoirVolume( inactiveRes ); + setStartReservoirVolume( inactiveRsrvr ); // Command DG to switch reservoirs cmdSetDGActiveReservoir( &rsrvrCmd ); @@ -647,7 +667,6 @@ reservoirSwitchStartTimeMS = getMSTimerCount(); state = TREATMENT_RESERVOIR_MGMT_WAIT_FOR_SWITCH_SETTLE_STATE; - } return state; @@ -704,4 +723,76 @@ return result; } +/*********************************************************************//** + * @brief + * The getTargetFillVolumeBasedOnDialysateFlowML function calculates the + * target fill volume in milliliters. + * @details Inputs: none + * @details Outputs: none + * @return Fill target volume in milliliters + *************************************************************************/ +static U32 getTargetFillVolumeBasedOnDialysateFlowML( void ) +{ + U32 targetFillVolumeML = FILL_RESERVOIR_TO_VOLUME_ML; + U32 dialysateFlowMLP = getTreatmentParameterU32( TREATMENT_PARAM_DIALYSATE_FLOW ); + + if ( dialysateFlowMLP >= RESERVOIR_FLOW_400_MLP ) + { + targetFillVolumeML = dialysateFlowMLP + FILL_VOLUME_INTERCEPT_ML_QD_ABOVE_400_MLPM; + } + + return targetFillVolumeML; +} + + +/************************************************************************* + * TEST SUPPORT FUNCTIONS + *************************************************************************/ + + +/*********************************************************************//** + * @brief + * The testSetRecirculationLevelPctOverride function overrides the recirculation + * percentage. + * @details Inputs: none + * @details Outputs: recirculationLevelPct + * @param value override percentage value with this + * @return TRUE if override successful, FALSE if not + *************************************************************************/ +BOOL testSetRecirculationLevelPctOverride( F32 value ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + result = TRUE; + recirculationLevelPct.ovData = value; + recirculationLevelPct.override = OVERRIDE_KEY; + } + + return result; +} + +/*********************************************************************//** + * @brief + * The testResetRecirculationLevelPctOverride function resets the override of the + * recirculation percentage. + * @details Inputs: none + * @details Outputs: recirculationLevelPct + * @return TRUE if reset successful, FALSE if not + *************************************************************************/ +BOOL testResetRecirculationLevelPctOverride( void ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + result = TRUE; + recirculationLevelPct.override = OVERRIDE_RESET; + recirculationLevelPct.ovData = recirculationLevelPct.ovInitData; + } + + return result; +} + /**@}*/