/************************************************************************** * * Copyright (c) 2021-2023 Diality Inc. - All Rights Reserved. * * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * @file Reservoirs.c * * @author (last) Sean Nash * @date (last) 16-Aug-2023 * * @author (original) Dara Navaei * @date (original) 21-Nov-2021 * ***************************************************************************/ #include // For reservoir management calculations #include "DGInterface.h" #include "Dialysis.h" #include "DialInFlow.h" #include "MessageSupport.h" #include "ModeTreatment.h" #include "ModeTreatmentParams.h" #include "NVDataMgmt.h" #include "OperationModes.h" #include "Reservoirs.h" #include "TaskGeneral.h" #include "Timers.h" // ********** private definitions ********** #define RESERVOIR_FRESH_SETTLE_TIME_MS 15000 ///< Allocated time to settle the freshly filled reservoir in milliseconds. #define RESERVOIR_USED_SETTLE_TIME_MS 5000 ///< Allocated time to settle the depleted reservoir in milliseconds. #define RESERVOIR_CYCLE_EXTRA_MARGIN_TIME_MS 8000 ///< Reservoir extra time in during the cycle for error in milliseconds. #define MAX_RESERVOIR_VOLUME_ML 1900.0F ///< Maximum allowed fluid in a reservoir in milliliters. #define MAX_RESERVOIR_DILUTION 0.15F ///< Maximum reservoir dilution limit. #define MAX_RESERVOIR_RECIRCULATION_400_MLP 1.1F ///< Maximum reservoir recirculation limit <= 400mL/m. #define MAX_RESERVOIR_RECIRCULATION_450_MLP 1.15F ///< Maximum reservoir recirculation limit <= 450mL/m. #define MAX_RESERVOIR_RECIRCULATION_500_MLP 1.2F ///< Maximum reservoir recirculation limit <= 500mL/m. #define MAX_RESERVOIR_RECIRCULATION_550_MLP 1.3F ///< Maximum reservoir recirculation limit <= 550mL/m. #define MAX_RESERVOIR_RECIRCULATION_600_MLP 1.4F ///< Maximum reservoir recirculation limit <= 600mL/m. #define RESERVOIR_FLOW_400_MLP 400.0F ///< Reservoir flow rate 400mL/m. #define RESERVOIR_FLOW_450_MLP 450.0F ///< Reservoir flow rate 450mL/m. #define RESERVOIR_FLOW_500_MLP 500.0F ///< Reservoir flow rate 500mL/m. #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 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. #define TGT_FILL_FLOW_ERROR_ALLOWANCE 0.9F ///< Target fill flow rate for dialysate allowed error percentage. #define DIA_FLOW_TO_FILL_FLOW_SECOND_ORDER_COEFF 10.0F ///< Dialysate flow rate to fill flow rate second order coefficient. #define DIA_FLOW_TO_FILL_FLOW_FIRST_ORDER_COEFF 7.5F ///< Dialysate flow rate to fill flow rate first order coefficient. #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). // ********** private data ********** /// States of the treatment reservoir management state machine. typedef enum TreatmentReservoirMgmt_States { TREATMENT_RESERVOIR_MGMT_START_STATE = 0, ///< Treatment reservoir management start state. TREATMENT_RESERVOIR_MGMT_DRAIN_RESERVOIR_STATE, ///< Treatment reservoir management drain reservoir state. TREATMENT_RESERVOIR_MGMT_WAIT_TO_FILL_STATE, ///< Treatment reservoir management wait to fill state. TREATMENT_RESERVOIR_MGMT_FILL_RESERVOIR_STATE, ///< Treatment reservoir management fill state. TREATMENT_RESERVOIR_MGMT_WAIT_FOR_FILL_SETTLE_STATE, ///< Treatment reservoir management wait for fill to settle state. TREATMENT_RESERVOIR_MGMT_WAIT_FOR_SWITCH_SETTLE_STATE, ///< Treatment reservoir management wait for switch to settles state. NUM_OF_TREATMENT_RESERVOIR_MGMT_STATES ///< Number of treatment reservoir management states. } TREATMENT_RESERVOIR_MGMT_STATE_T; static TREATMENT_RESERVOIR_MGMT_STATE_T reservoirsState; ///< Treatment mode's reservoirs state. static U32 timeStartMS; ///< Active reservoir start time in milliseconds. static U32 timeDepleteMS; ///< Active reservoir depletion time in milliseconds. static F32 volSpentML; ///< Active reservoir spent volume in milliliters. static U32 reservoirPublicationCounter; ///< Reservoirs data publication timer counter. static F32 dilutionLevelPct; ///< Reservoir dilution level. static DG_OP_MODE_T dgOpMode; ///< DG operation mode. static U32 dgSubMode; ///< DG operation submode. static U32 timeReservoirInUse; ///< Reservoir time in use in general task intervals. static U32 timeReservoirInUF; ///< Reservoir time in ultrafiltration (in ms). 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 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 const F32 RESERVOIR_DILUTION_RATIO = MAX_RESERVOIR_DILUTION / ( 1.0 - MAX_RESERVOIR_DILUTION ); ///< Reservoir dilution ratio. // ********** private function prototypes ********** // Reservoir management functions static void checkReservoirMaxVolume( void ); static F32 getTargetFillFlowRateLPM( void ); static U32 getFillTimeMS( void ); static void calculateActiveReservoirCycleTime( void ); static F32 getReservoirRecirculationMaxPercent( void ); static void publishReservoirData( void ); static BOOL isDialysateTempAlarmActive( void ); static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtStartState( void ); static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtDrainState( void ); static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtWaitToFillState( void ); static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtFillState( void ); static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtWaitForFillSettleState( void ); static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtWaitForSwitchSettleState( void ); /*********************************************************************//** * @brief * The initReservoirs function initializes the treatment reservoir management * state machine. * @details Inputs: none * @details Outputs: reservoirsState, timeStartMS, timeDepletionMS, volTotalMl, * volSpentMl, reservoirsPublicationCounter, dilutionLevelPct, dgOpMode, dgSubMode, * timeReservoirInUse, volSpentUFML, activeReservoir, recirculationLevelPct, * reservoirSwitchStartTimeMS, timeWaitToFillMS, targetFillFlowLPM, ratios * previousDialysateFlowRate * @return none *************************************************************************/ void initReservoirs( void ) { // Initialize the variables reservoirsState = TREATMENT_RESERVOIR_MGMT_START_STATE; timeStartMS = getMSTimerCount(); timeDepleteMS = 0; volSpentML = 0.0F; reservoirPublicationCounter = 0; dilutionLevelPct = 0.0F; dgOpMode = DG_MODE_INIT; dgSubMode = 0; timeReservoirInUse = 0; timeReservoirInUF = 0; lastTimeReservoirInUF = 0; volSpentUFML = 0.0F; activeReservoir = getDGActiveReservoir(); recirculationLevelPct = 0.0F; reservoirSwitchStartTimeMS = 0; timeWaitToFillMS = 0; targetFillFlowLPM = 0.0F; previousDialysateFlowMLP = 0; previousUFFlowMLP = 0.0F; ratios.acidMixingRatio = 0.0F; ratios.bicarbMixingRatio = 0.0F; ratios.timeFillPrepMS = 0; } /*********************************************************************//** * @brief * The resetReservoirsVariables function resets the reservoirs variables for * the next run. * @details Inputs: none * @details Outputs: timeStartMS * @return none *************************************************************************/ void resetReservoirsVariables( void ) { timeStartMS = getMSTimerCount(); } /*********************************************************************//** * @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 * @return none *************************************************************************/ void execReservoirs( void ) { U32 msSinceLastVolumeCalc = calcTimeSince( timeStartMS ); F32 flowRateMLPerMS = (F32)getTargetDialInFlowRate() / (F32)( MS_PER_SECOND * SEC_PER_MIN ); // Updated the static variables activeReservoir = getDGActiveReservoir(); dgOpMode = getDGOpMode(); dgSubMode = getDGSubMode(); ratios = getDGMixingRatios(); checkReservoirMaxVolume(); calculateActiveReservoirCycleTime(); // Calculate volume used from active reservoir - do not accumulate if not performing dialysis or saline bolus is in progress if ( ( TREATMENT_DIALYSIS_STATE == getTreatmentState() ) && ( getDialysisState() != DIALYSIS_SALINE_BOLUS_STATE ) ) { volSpentML += ( flowRateMLPerMS * msSinceLastVolumeCalc ); timeReservoirInUse++; // Check the recirculation level recirculationLevelPct = volSpentML / (F32)FILL_RESERVOIR_TO_VOLUME_ML; } // Update the reservoir start time timeStartMS = getMSTimerCount(); switch( reservoirsState ) { case TREATMENT_RESERVOIR_MGMT_START_STATE: reservoirsState = handleReservoirMgmtStartState(); break; case TREATMENT_RESERVOIR_MGMT_DRAIN_RESERVOIR_STATE: reservoirsState = handleReservoirMgmtDrainState(); break; case TREATMENT_RESERVOIR_MGMT_WAIT_TO_FILL_STATE: reservoirsState = handleReservoirMgmtWaitToFillState(); break; case TREATMENT_RESERVOIR_MGMT_FILL_RESERVOIR_STATE: reservoirsState = handleReservoirMgmtFillState(); break; case TREATMENT_RESERVOIR_MGMT_WAIT_FOR_FILL_SETTLE_STATE: reservoirsState = handleReservoirMgmtWaitForFillSettleState(); break; case TREATMENT_RESERVOIR_MGMT_WAIT_FOR_SWITCH_SETTLE_STATE: reservoirsState = handleReservoirMgmtWaitForSwitchSettleState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_HD_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_TREATMENT_RESERVOIR_MANAGEMENT_STATE, (U32)reservoirsState ) reservoirsState = TREATMENT_RESERVOIR_MGMT_START_STATE; break; } publishReservoirData(); } /*********************************************************************//** * @brief * The getLastReservoirUFTimeInMs function returns the reservoir ultrafiltration * time (in ms) for the last reservoir used in treatment. * @details Inputs: none * @details Outputs: none * @return lastTimeReservoirInUF *************************************************************************/ U32 getLastReservoirUFTimeInMs( void ) { return lastTimeReservoirInUF; } /*********************************************************************//** * @brief * The updateReservoirUFTime function updates the reservoir ultrafiltration * time (in ms) for currently active reservoir used in treatment. * @details Inputs: timeReservoirInUF * @details Outputs: timeReservoirInUF * @return lastTimeReservoirInUF *************************************************************************/ void updateReservoirUFTime( void ) { timeReservoirInUF += TASK_GENERAL_INTERVAL; } // ********** private functions ********** /*********************************************************************//** * @brief * The checkReservoirMaxVolume function checks whether the active reservoir's * filled volume has exceeded maximum allowed value in milliliters. If it has * exceeded, it raises an alarm. * @details Inputs: none * @details Outputs: none * @return none *************************************************************************/ static void checkReservoirMaxVolume( void ) { DG_RESERVOIR_ID_T active = getDGActiveReservoir(); if ( getReservoirWeight( active ) >= MAX_RESERVOIR_VOLUME_ML ) { SET_ALARM_WITH_1_F32_DATA( ALARM_ID_HD_ACTIVE_RESERVOIR_WEIGHT_OUT_OF_RANGE, getReservoirWeight( active ) ) } } /*********************************************************************//** * @brief * The getTargetFillFlowRateLPM function sets the target fill flow rate for DG * based on the target dialysate flow rate. * @details Inputs: none * @details Outputs: targetFillFlowLPM * @return target fill flow rate *************************************************************************/ static F32 getTargetFillFlowRateLPM( void ) { F32 fillFlowRate = TGT_FILL_FLOW_800_ML_PER_MIN; targetFillFlowLPM = fillFlowRate; return fillFlowRate; } /*********************************************************************//** * @brief * The getFillTimeMS function calculates the fill time in milliseconds. * @details Inputs: ratios * @details Outputs: none * @return target maximum fill time *************************************************************************/ static U32 getFillTimeMS( void ) { // Get target flow * error allowance to make sure there is enough time to fill F32 targetFillFlowRate = getTargetFillFlowRateLPM() * TGT_FILL_FLOW_ERROR_ALLOWANCE; // The target fill flow rate is RO flow rate plus acid and bicarb flow rates 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; // Add the prepare time in the DG mode fill to the calculated fill time in milliseconds U32 timeTotalFillMS = timeFillMS + ratios.timeFillPrepMS; return timeTotalFillMS; } /*********************************************************************//** * @brief * The calculateActiveReservoirCycleTime function calculates the active reservoir * cycle time in milliseconds. If the target dialysate flow or target ultrafilter * 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 * @return none *************************************************************************/ static void calculateActiveReservoirCycleTime( void ) { 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 targetUFFlowMLP = getCurrentUFSetRate(); F32 timeDepletionMS = ( (F32)FILL_RESERVOIR_TO_VOLUME_ML / 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 timeFreshRemainingMS = ( volFreshML / (F32)dialysateFlowMLP ) * SEC_PER_MIN * MS_PER_SECOND; F32 volMaxUFML = FILL_RESERVOIR_TO_VOLUME_ML * 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; // 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 // The depletion time is then used to calculate the time to wait to fill and whether to trigger a fill command or not F32 volRemainingUFML = volMaxUFML - volSpentUFML; F32 timeDepleteRemainingMS = ( volRemainingUFML / targetUFFlowMLP ) * SEC_PER_MIN * MS_PER_SECOND; timeDepleteMS = MIN( timeFreshRemainingMS, timeDepleteRemainingMS ); } else { timeDepleteMS = timeFreshRemainingMS; } timeDepletionMS = MIN( timeDepletionMS, timeUFDepletionMS ); timeReservoirCycleMS = MAX( timeTotalCycleMS, timeDepletionMS ); // If the target dialysate flow rate has changed, it means the cycle time (depletion time) has changed // So the new value has to be sent again. if ( ( previousDialysateFlowMLP != dialysateFlowMLP ) || ( previousUFFlowMLP != targetUFFlowMLP ) ) { DG_CMD_DIALYSATE_HEATING_PARAMS_T params; params.trimmerTargetTemperature = getTreatmentParameterF32( TREATMENT_PARAM_DIALYSATE_TEMPERATURE ); // TODO timeReservoirWait2SwitchMS Should this be the calculated value? Or just RESERVOIR_FRESH_SETTLE_TIME_MS? params.timeReservoirWait2SwitchMS = RESERVOIR_FRESH_SETTLE_TIME_MS + RESERVOIR_CYCLE_EXTRA_MARGIN_TIME_MS; params.timeReservoirFillMS = fillTimeMS; params.timeReservoirCycleMS = timeReservoirCycleMS; params.dialysateFlowLPM = ( (F32)dialysateFlowMLP ) / ML_PER_LITER; params.usePriTargetTempEquation = TRUE; cmdSetDGDialysateHeatingParams( params ); // Update the previous dialysate flow and UF flow regardless of which one changed // If anything of have not changed, then the update does not change anything previousDialysateFlowMLP = dialysateFlowMLP; previousUFFlowMLP = targetUFFlowMLP; } } /*********************************************************************//** * @brief * The getReservoirRecirculationMaxPercent function returns the reservoir management * maximum recirculation percentage. * @details Inputs: TREATMENT_PARAM_DIALYSATE_FLOW * @details Outputs: none * @return reservoir management max recirculation percent *************************************************************************/ static F32 getReservoirRecirculationMaxPercent( void ) { U32 targetDialysateFlowMLP = getTreatmentParameterU32( TREATMENT_PARAM_DIALYSATE_FLOW ); F32 maxPercent = 0.0F; if ( targetDialysateFlowMLP <= RESERVOIR_FLOW_400_MLP ) { maxPercent = MAX_RESERVOIR_RECIRCULATION_400_MLP; } else if ( targetDialysateFlowMLP <= RESERVOIR_FLOW_450_MLP ) { maxPercent = MAX_RESERVOIR_RECIRCULATION_450_MLP; } else if ( targetDialysateFlowMLP <= RESERVOIR_FLOW_500_MLP ) { maxPercent = MAX_RESERVOIR_RECIRCULATION_500_MLP; } else if ( targetDialysateFlowMLP <= RESERVOIR_FLOW_550_MLP ) { maxPercent = MAX_RESERVOIR_RECIRCULATION_550_MLP; } else { maxPercent = MAX_RESERVOIR_RECIRCULATION_600_MLP; } return maxPercent; } /*********************************************************************//** * @brief * The publishReservoirData function publishes reservoirs data during treatment. * @details Inputs: reservoirsPublicationCounter * @details Outputs: reservoirsPublicationCounter * @return none *************************************************************************/ static void publishReservoirData( void ) { if ( ++reservoirPublicationCounter >= RESERVOIR_DATA_PUB_INTERVAL ) { RESERVOIRS_MANAGEMENT_DATA_T data; data.reservoirsExecState = (U32)reservoirsState; data.activeReservoirUFVolML = volSpentUFML; data.activeReservoirVolSpentML = volSpentML; data.dilLevelPct = dilutionLevelPct * 100; data.recircLevelPct = recirculationLevelPct * 100; data.timeDepletionMS = timeDepleteMS; data.timeWaitFillMS = timeWaitToFillMS; data.tempRemoveTargetFillFlow = targetFillFlowLPM; broadcastData( MSG_ID_HD_RESERVOIRS_DATA, COMM_BUFFER_OUT_CAN_HD_BROADCAST, (U08*)&data, sizeof( RESERVOIRS_MANAGEMENT_DATA_T ) ); reservoirPublicationCounter = 0; } } /*********************************************************************//** * @brief * The handleReservoirMgmtStartState function executes the reservoir management * start state. * @details Inputs: dgOpMode * @details Outputs: none * @return next reservoir management state of the state machine *************************************************************************/ static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtStartState( void ) { TREATMENT_RESERVOIR_MGMT_STATE_T state = TREATMENT_RESERVOIR_MGMT_START_STATE; if ( DG_MODE_GENE == dgOpMode ) { if ( DG_GEN_IDLE_MODE_STATE_FLUSH_WATER == getDGSubMode() ) { cmdStartDGDrain( DRAIN_RESERVOIR_TO_VOLUME_ML, TRUE, FALSE, TRUE ); } } else if ( DG_MODE_DRAI == dgOpMode ) { state = TREATMENT_RESERVOIR_MGMT_DRAIN_RESERVOIR_STATE; } return state; } /*********************************************************************//** * @brief * The handleReservoirMgmtDrainState function executes the reservoir management * drain state. * @details Inputs: dgOpMode * @details Outputs: none * @return next reservoir management state of the state machine *************************************************************************/ static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtDrainState( void ) { TREATMENT_RESERVOIR_MGMT_STATE_T state = TREATMENT_RESERVOIR_MGMT_DRAIN_RESERVOIR_STATE; if ( DG_MODE_GENE == dgOpMode ) { state = TREATMENT_RESERVOIR_MGMT_WAIT_TO_FILL_STATE; } return state; } /*********************************************************************//** * @brief * The handleReservoirMgmtWaitToFillState function executes the reservoir management * wait to fill state. * @details Inputs: volSpentML, dgOpMode, dgSubMode * @details Outputs: dilutionLevelPct, volSpentUFML, timeDepleteMS, timeWaitToFillMS * @return next reservoir management state of the state machine *************************************************************************/ static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtWaitToFillState( void ) { 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(); // 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; // 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 ( DG_GEN_IDLE_MODE_STATE_FLUSH_WATER == dgSubMode ) { cmdStartDGFill( FILL_RESERVOIR_TO_VOLUME_ML, 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 ); } } else { // Time to wait prior to next fill is depletion time - the whole group of how much time is needed to fill a reservoir + // the time it takes to wait for a reservoir to settle and the extra margin time for ramp up of the RO and drain pumps and // any other extra times. timeWaitToFillMS = timeDepleteMS - ( getFillTimeMS() + RESERVOIR_FRESH_SETTLE_TIME_MS + RESERVOIR_CYCLE_EXTRA_MARGIN_TIME_MS ); // If the wait time has elapsed, trigger a fill command if ( timeWaitToFillMS <= 0 ) { cmdStartDGFill( FILL_RESERVOIR_TO_VOLUME_ML, targetFillFlowRateLPM ); } } if ( DG_MODE_FILL == dgOpMode ) { // Reset the variables for the next wait to fill cycle timeDepleteMS = 0; timeWaitToFillMS = 0; state = TREATMENT_RESERVOIR_MGMT_FILL_RESERVOIR_STATE; } return state; } /*********************************************************************//** * @brief * The handleReservoirMgmtFillState function executes the reservoir management * fill state. * @details Inputs: recirculationLevelPct, dgOpMode, dgSubMode * @details Outputs: recirculationLevelPct, reservoirSwitchStartTimeMS * @return next reservoir management state of the state machine *************************************************************************/ static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtFillState( void ) { TREATMENT_RESERVOIR_MGMT_STATE_T state = TREATMENT_RESERVOIR_MGMT_FILL_RESERVOIR_STATE; // 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() ) { #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 ) } } // Check if DG has moved out of the fill mode if ( ( DG_MODE_GENE == dgOpMode ) && ( DG_GEN_IDLE_MODE_STATE_FLUSH_WATER == dgSubMode ) ) { // Clear any of the recoverable conditions in case they were raised during the fill or wait to fill clearAlarmCondition( ALARM_ID_HD_ACTIVE_RESERVOIR_RECIRCULATION_OUT_OF_RANGE ); clearAlarmCondition( ALARM_ID_HD_ACTIVE_RESERVOIR_WEIGHT_OUT_OF_RANGE ); reservoirSwitchStartTimeMS = getMSTimerCount(); state = TREATMENT_RESERVOIR_MGMT_WAIT_FOR_FILL_SETTLE_STATE; } return state; } /*********************************************************************//** * @brief * The handleReservoirMgmtWaitForFillSettleState function executes the reservoir * management wait for fill to settle state. * @details Inputs: reservoirSwitchStartTimeMS, volSpentML * @details Outputs: reservoirSwitchStartTimeMS, volSpentML, volSpentUFML, dilutionLevelPct * @return next reservoir management state of the state machine *************************************************************************/ 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(); // 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; // 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() ) ) ) { DG_SWITCH_RSRVRS_CMD_T rsrvrCmd; DG_RESERVOIR_ID_T inactiveRes = getDGInactiveReservoir(); rsrvrCmd.reservoirID = (U32)inactiveRes; rsrvrCmd.useLastTrimmerHeaterDC = TRUE; // Signal dialysis sub-mode to capture baseline volume for next reservoir. setStartReservoirVolume( inactiveRes ); // Command DG to switch reservoirs cmdSetDGActiveReservoir( &rsrvrCmd ); // Signal dialysis sub-mode to switch reservoirs signalReservoirsSwitched(); // Get ready for the next delivery volSpentML = 0.0; // Wait for used reservoir to settle reservoirSwitchStartTimeMS = getMSTimerCount(); state = TREATMENT_RESERVOIR_MGMT_WAIT_FOR_SWITCH_SETTLE_STATE; } return state; } /*********************************************************************//** * @brief * The handleReservoirMgmtWaitForSwitchSettleState function executes the reservoir * management wait for switch to settle state. * @details Inputs: reservoirSwitchStartTimeMS * @details Outputs: timeReservoirInUse * @return next reservoir management state of the state machine *************************************************************************/ static TREATMENT_RESERVOIR_MGMT_STATE_T handleReservoirMgmtWaitForSwitchSettleState( void ) { TREATMENT_RESERVOIR_MGMT_STATE_T state = TREATMENT_RESERVOIR_MGMT_WAIT_FOR_SWITCH_SETTLE_STATE; if ( TRUE == didTimeout( reservoirSwitchStartTimeMS, RESERVOIR_USED_SETTLE_TIME_MS ) ) { // Switching the active reservoir so reset the reservoir in use timer lastTimeReservoirInUF = timeReservoirInUF; timeReservoirInUF = 0; timeReservoirInUse = 0; // Signal dialysis sub-mode to capture final volume of prior reservoir after settling. setFinalReservoirVolume(); // Reset to start state to restart drain, fill, switch process. state = TREATMENT_RESERVOIR_MGMT_START_STATE; } return state; } /*********************************************************************//** * @brief * The isDialysateTempAlarmActive function checks for active dialysate * temperature alarms * @details Inputs: none * @details Outputs: none * @return True if dialysate temperature alarm is active. False if not. *************************************************************************/ static BOOL isDialysateTempAlarmActive( void ) { BOOL result = FALSE; if ( ( TREATMENT_STOP_STATE == getTreatmentState() ) && ( TRUE == isAlarmConditionDetected( ALARM_ID_HD_DIALYSATE_TEMP_BELOW_TARGET_TEMP ) || TRUE == isAlarmConditionDetected( ALARM_ID_HD_DIALYSATE_TEMP_ABOVE_SAFETY_TEMP ) || TRUE == isAlarmConditionDetected( ALARM_ID_HD_DIALYSATE_TEMP_ABOVE_TARGET_TEMP ) ) ) { result = TRUE; } return result; } /**@}*/