// TODO the copyright will be added automatically #include #include "DGInterface.h" #include "Dialysis.h" #include "DialInFlow.h" #include "MessageSupport.h" #include "ModeTreatment.h" #include "OperationModes.h" #include "Reservoirs.h" #include "TaskGeneral.h" #include "Timers.h" // ********** private definitions ********** #define RESERVOIR_SETTLE_TIME_MS 5000 ///< Allocated time to settle the filled reservoir in milliseconds. #define MAX_RESERVOIR_VOLUME_ML 1950.0 ///< Maximum allowed fluid in a reservoir in milliliters. #define MAX_RESERVOIR_DILUTION 0.15 ///< Maximum reservoir dilution limit. #define MAX_RESERVOIR_RECIRCULATION 0.1 ///< Maximum reservoir recirculation limit. #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 DIALYSATE_FLOW_RATE_100_ML_PER_MIN 0.1 ///< Dialysate flow rate 100 mL/min. #define DIALYSATE_FLOW_RATE_350_ML_PER_MIN 0.35 ///< Dialysate flow rate 350 mL/min. #define DIALYSATE_FLOW_RATE_400_ML_PER_MIN 0.4 ///< Dialysate flow rate 400 mL/min. #define DIALYSATE_FLOW_RATE_500_ML_PER_MIN 0.5 ///< Dialysate flow rate 500 mL/min. #define DIALYSATE_FLOW_RATE_550_ML_PER_MIN 0.55 ///< Dialysate flow rate 550 mL/min. #define DIALYSATE_FLOW_RATE_600_ML_PER_MIN 0.6 ///< Dialysate flow rate 600 mL/min. #define TGT_FILL_FLOW_FOR_DIA_FLOW_100_TO_350_ML_PER_MIN 0.5 ///< Target fill flow rate for dialysate flow rates in between 100 to 350 mL/min. #define TGT_FILL_FLOW_FOR_DIA_FLOW_550_TO_600_ML_PER_MIN 0.8 ///< Target fill flow rate for dialysate flow rates in between 500 to 600 mL/min. #define DIA_FLOW_TO_FILL_FLOW_SECOND_ORDER_COEFF 10.0 ///< Dialysate flow rate to fill flow rate second order coefficient. #define DIA_FLOW_TO_FILL_FLOW_FIRST_ORDER_COEFF 7.5 ///< Dialysate flow rate to fill flow rate first order coefficient. #define DIA_FLOW_TO_FILL_FLOW_CONSTANT 2.0 ///< Dialysate flow rate to fill flow rate constant. // ********** 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; // TODO update the init function with all the newest variables static TREATMENT_RESERVOIR_MGMT_STATE_T reservoirsState; ///< Treatment mode's reservoirs state. static U32 timeStartMS = 0; ///< Active reservoir start time in milliseconds. static U32 timeDepleteMS = 0; ///< Active reservoir depletion time in milliseconds. static F32 volTotalML = 0.0; ///< Active reservoir total volume in milliliters. static F32 volSpentML = 0.0; ///< Active reservoir spent volume in milliliters. static U32 reservoirPublicationCounter = 0; ///< Reservoirs data publication timer counter. static F32 dilutionLevel = 0.0; static DG_OP_MODE_T dgOpMode = DG_MODE_INIT; static U32 dgSubMode = 0; static U32 timeReservoirInUseMS = 0; static F32 volSpentUFML = 0.0; static DG_RESERVOIR_ID_T activeReservoir; static F32 recirculationLevelPct = 0.0; static U32 reservoirSwitchStartTime = 0; static U32 timeFillReservoirMS = 0; static U32 timeWaitToFillMS = 0; // TODO set the reservoirs alarms properties in the alarmsDefs.h so the alarms will be recoverable // ********** private function prototypes ********** // Reservoir management functions static void checkReservoirDepletionTime( void ); static void checkReservoirMaxVolume( void ); static F32 getTargetFillFlowRateLPM( void ); static U32 getFillTimeMS( void ); static void publishReservoirData( 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 TODO add all the variables later * @return none *************************************************************************/ void initReservoirs( void ) { // TODO don't forget to update the variables and the doxygen reservoirsState = TREATMENT_RESERVOIR_MGMT_START_STATE; timeStartMS = 0; timeDepleteMS = 0; volTotalML = 0.0; volSpentML = 0.0; reservoirPublicationCounter = 0; } /*********************************************************************//** * @brief * The execReservoirs function executes the state machine for the treatment * reservoir management during treatment mode. * @details Inputs: reservoirsStatus * @details Outputs: reservoirsStatus * @return none *************************************************************************/ void execReservoirs( void ) { U32 msSinceLastVolumeCalc = calcTimeSince( timeStartMS ); F32 flowRateMLPerMS = (F32)getTargetDialInFlowRate() / (F32)( MS_PER_SECOND * SEC_PER_MIN ); activeReservoir = getDGActiveReservoir(); dgOpMode = getDGOpMode(); dgSubMode = getDGSubMode(); checkReservoirDepletionTime(); checkReservoirMaxVolume(); // TODO do we need this? Should we not call the reservoir management exec function in saline bolus? // Calculate volume used from active reservoir - do not accumulate if saline bolus is in progress if ( getSalineBolusState() != SALINE_BOLUS_STATE_IN_PROGRESS ) { volSpentML += ( flowRateMLPerMS * msSinceLastVolumeCalc ); } // 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; } publishReservoirData(); } // ********** private functions ********** /*********************************************************************//** * @brief * The checkReservoirDepletionTime function checks whether the active reservoir's * depletion time has elapsed or not. If it has elapsed, it raises an alarm. * @details Inputs: timeStartMS * @details Outputs: none * @return none *************************************************************************/ static void checkReservoirDepletionTime( void ) { // Check if the time that the reservoir has been use has exceeded the limit if ( TRUE == didTimeout( timeReservoirInUseMS, MAX_RESERVOIR_DEPLETION_TIME_MS ) ) { SET_ALARM_WITH_1_U32_DATA( ALARM_ID_HD_ACTIVE_RESERVOIR_DEPLETION_TIME_OUT, calcTimeSince( timeReservoirInUseMS ) ) } } /*********************************************************************//** * @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: none * @return target fill flow rate *************************************************************************/ static F32 getTargetFillFlowRateLPM( void ) { F32 fillFlowRate = 0.0; F32 dialysateFlow = getDGDialysateFlowRateLMin(); if ( ( dialysateFlow >= DIALYSATE_FLOW_RATE_100_ML_PER_MIN ) && ( dialysateFlow <= DIALYSATE_FLOW_RATE_350_ML_PER_MIN ) ) { fillFlowRate = TGT_FILL_FLOW_FOR_DIA_FLOW_100_TO_350_ML_PER_MIN; } else if ( ( dialysateFlow >= DIALYSATE_FLOW_RATE_400_ML_PER_MIN ) && ( dialysateFlow <= DIALYSATE_FLOW_RATE_500_ML_PER_MIN ) ) { fillFlowRate = pow( dialysateFlow, 2 ) * DIA_FLOW_TO_FILL_FLOW_SECOND_ORDER_COEFF - dialysateFlow * DIA_FLOW_TO_FILL_FLOW_FIRST_ORDER_COEFF + DIA_FLOW_TO_FILL_FLOW_CONSTANT; } else if ( ( dialysateFlow >= DIALYSATE_FLOW_RATE_550_ML_PER_MIN ) && ( dialysateFlow <= DIALYSATE_FLOW_RATE_600_ML_PER_MIN ) ) { fillFlowRate = TGT_FILL_FLOW_FOR_DIA_FLOW_550_TO_600_ML_PER_MIN; } else { // TODO fault if the flow is not in range? } return fillFlowRate; } /*********************************************************************//** * @brief * The getFillTimeMS function calculates the fill time in milliseconds. * @details Inputs: none * @details Outputs: none * @return target fill flow rate *************************************************************************/ static U32 getFillTimeMS( void ) { DG_MIXING_RATIOS_T ratios = getDGMixingRatios(); F32 targetFillFlowRate = getTargetFillFlowRateLPM(); F32 totalTargetFillFlow = targetFillFlowRate + ( targetFillFlowRate * ratios.acidMixingRatio ) + ( targetFillFlowRate * ratios.bicarbMixingRatio ); U32 timeFillMS = ( FILL_RESERVOIR_TO_VOLUME_ML / ( ML_PER_LITER * totalTargetFillFlow ) ) * SEC_PER_MIN * MS_PER_SECOND; U32 timeTotalFillMS = timeFillMS + ratios.fillPrepTimeMS; return timeTotalFillMS; } /*********************************************************************//** * @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; 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: none * @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: none * @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: dilutionLevel, volSpentML, dgSubMode * @details Outputs: dilutionLevel, volSpentML, volSpentUFML, timeDepletionMS * @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; // Calculate the dilution level dilutionLevel = volSpentML / (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 if ( ( dilutionLevel >= 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, getTargetFillFlowRateLPM() ); } state = TREATMENT_RESERVOIR_MGMT_FILL_RESERVOIR_STATE; } else { F32 volFreshML = FILL_RESERVOIR_TO_VOLUME_ML - volSpentML; F32 timeFreshRemainingMS = volFreshML / ( getDGDialysateFlowRateLMin() * 1000.0 ); F32 volMaxUFML = FILL_RESERVOIR_TO_VOLUME_ML * MAX_RESERVOIR_DILUTION; volSpentUFML = getReservoirUltrafiltrationVol( activeReservoir ); F32 volRemainingUFML = volMaxUFML - volSpentUFML; F32 timeDepleteRemainingMS = ( volRemainingUFML / getCurrentUFSetRate() ) * SEC_PER_MIN * MS_PER_SECOND; timeDepleteMS = MIN( timeFreshRemainingMS, timeDepleteRemainingMS ); timeWaitToFillMS = timeFillReservoirMS - reservoirSwitchStartTime; if ( timeDepleteMS >= timeWaitToFillMS ) { cmdStartDGFill( FILL_RESERVOIR_TO_VOLUME_ML, getTargetFillFlowRateLPM() ); } 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, reservoirSwitchTimer * @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; // Check the recirculation level recirculationLevelPct = ( (F32)FILL_RESERVOIR_TO_VOLUME_ML - volSpentML ) / (F32)FILL_RESERVOIR_TO_VOLUME_ML; // 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 >= MAX_RESERVOIR_RECIRCULATION ) { SET_ALARM_WITH_1_F32_DATA( ALARM_ID_HD_ACTIVE_RESERVOIR_RECIRCULATION_OUT_OF_RANGE, recirculationLevelPct ) } 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_DEPLETION_TIME_OUT ); clearAlarmCondition( ALARM_ID_HD_ACTIVE_RESERVOIR_WEIGHT_OUT_OF_RANGE ); reservoirSwitchStartTime = 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: reservoirSwitchTimer * @details Outputs: reservoirSwitchTimer, volSpentML * @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; // Wait for the reservoir to settle and then send the commands to switch the active reservoir if ( TRUE == didTimeout( reservoirSwitchStartTime, RESERVOIR_SETTLE_TIME_MS ) ) { DG_RESERVOIR_ID_T inactiveRes = getDGInactiveReservoir(); // Signal dialysis sub-mode to capture baseline volume for next reservoir. setStartReservoirVolume( inactiveRes ); // Command DG to switch reservoirs cmdSetDGActiveReservoir( inactiveRes ); // Signal dialysis sub-mode to switch reservoirs signalReservoirsSwitched(); // Get ready for the next delivery volSpentML = 0.0; timeFillReservoirMS = getFillTimeMS(); // Wait for used reservoir to settle reservoirSwitchStartTime = 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: reservoirSwitchTimer * @details Outputs: none * @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( reservoirSwitchStartTime, RESERVOIR_SETTLE_TIME_MS ) ) { // 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; } /**@}*/