/************************************************************************** * * Copyright (c) 2019-2020 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 Prime.h * * @author (last) Quang Nguyen * @date (last) 08-Dec-2020 * * @author (original) Quang Nguyen * @date (original) 08-Dec-2020 * ***************************************************************************/ #include "AirTrap.h" #include "AlarmMgmt.h" #include "BloodFlow.h" #include "DialInFlow.h" #include "DialOutFlow.h" #include "DGInterface.h" #include "Prime.h" #include "TaskGeneral.h" #include "Timers.h" #include "Valves.h" /** * @addtogroup Prime * @{ */ // ********** private definitions ********** #define BLOOD_PUMP_FLOW_RATE_PURGE_AIR 100 ///< Blood pump flow rate during prime purge air state. #define BLOOD_PUMP_FLOW_RATE_CIRC_BLOOD_CIRCUIT 300 ///< Blood pump flow rate during prime recirculate blood circuit state. #define DIALYSATE_PUMP_PRIME_FLOW_RATE 300 ///< Dialysate pump flow rate during priming fluid path. #define LOAD_CELL_VOLUME_NOISE_TOLERANCE 0.05 ///< Allow 5% tolerance on load cell readings. #define PRIME_DRAIN_RESERVOIR_TO_VOLUME_ML 100 ///< Drain reservoir to this volume (in mL) during prime. #define PRIME_FILL_RESERVOIR_TO_VOLUME_ML ( FILL_RESERVOIR_TO_VOLUME_ML / 2 ) ///< Fill reservoir to this volume (in mL) during prime. #define NO_AIR_DETECTED_COUNT ( 10 * MS_PER_SECOND ) ///< No air detected time period count. #define PURGE_AIR_TIME_OUT_COUNT ( 30 * MS_PER_SECOND ) ///< Time period count for purge air time out. #define MIN_LOAD_CELL_STEADY_VOLUME_TIME ( 10 * MS_PER_SECOND ) ///< Minimum time load cell reading need to remain steady in ms. #define PRIME_DIALYSATE_DIALYZER_TIME_LIMIT ( 120 * MS_PER_SECOND ) ///< Time limit for priming dialysate dialyzer circuit. #define PRIME_DIALYSATE_BYPASS_TIME_LIMIT ( 120 * MS_PER_SECOND ) ///< Time limit for priming dialysate bypass circuit. /// States of the treatment reservoir management state machine. typedef enum PrimeReservoirMgmt_States { PRIME_RESERVOIR_MGMT_START_STATE = 0, ///< If DG not already in re-circ mode, try to get it there. PRIME_RESERVOIR_MGMT_FLUSH_DG_LINES_STATE, ///< In DG re-circ, wait for lines to flush - then start draining inactive reservoir. PRIME_RESERVOIR_MGMT_DRAIN_STATE, ///< Wait for drain to complete. PRIME_RESERVOIR_MGMT_START_FILL_STATE, ///< Command DG to start filling reservoir. PRIME_RESERVOIR_MGMT_FILL_STATE, ///< Wait for fill to complete. PRIME_RESERVOIR_MGMT_FILL_COMPLETE_STATE, ///< Reservoir fill has completed. NUM_OF_PRIME_RESERVOIR_MGMT_STATES ///< Number of prime reservoir mgmt. states. } PRIME_RESERVOIR_MGMT_STATE_T; // ********** private data ********** static PRE_TREATMENT_PRIME_STATE_T currentPrimeState; ///< Current state of the prime sub-mode state machine. static PRIME_RESERVOIR_MGMT_STATE_T currentReservoirMgmtState; ///< Current reservoir management state. static BOOL isPrimeCompleted; ///< Status if prime sequence has been completed. static BOOL primeStartReqReceived; ///< Flag to indicate if a request to start priming has been received. static BOOL reservoirFilledStatus[ NUM_OF_DG_RESERVOIRS ]; ///< Flag to indicate if a reservoir has been filled. static U32 noAirDetectedStartTime; ///< starting time when detecting no air. static U32 purgeAirTimeOutStartTime; ///< Starting time for purge air state time out. static U32 primeDialysateDialyzerStartTime; ///< Starting time of priming dialysate dialyzer circuit. static U32 primeDialysateBypassStartTime; ///< Starting time of priming dialysate bypass circuit. static U32 previousLoadCellReading; ///< Previous load cell reading. static U32 loadcellSteadyVolumeStartTime; ///< Load cell steady volume starting time. // ********** private function prototypes ********** static void execPreTreatmentReservoirMgmt( void ); static void purgeAirValvesBloodPumpControl( void ); static PRE_TREATMENT_PRIME_STATE_T handlePrimeSalineSetupState( void ); static PRE_TREATMENT_PRIME_STATE_T handlePrimePurgeAirState( void ); static PRE_TREATMENT_PRIME_STATE_T handlePrimeCircBloodCircuitState( void ); static PRE_TREATMENT_PRIME_STATE_T handlePrimeReservoirOneFillCompleteState( void ); static PRE_TREATMENT_PRIME_STATE_T handlePrimeDialysateDialyzerState( void ); static PRE_TREATMENT_PRIME_STATE_T handlePrimeReservoirTwoFillCompleteState( void ); static PRE_TREATMENT_PRIME_STATE_T handlePrimeDialysateBypassState( void ); /*********************************************************************//** * @brief * The initPrime function initializes the prime sub-mode module. * This function will reset anything required before the start of priming sequence. * @details Inputs: none * @details Outputs: Prime sub-mode module initialized. * @return none *************************************************************************/ void initPrime( void ) { transitionToPrime(); } /*********************************************************************//** * @brief * The transitionToPrime function prepares for transition to prime sub-mode. * This function will reset anything required before the start of priming sequence. * @details Inputs: none * @details Outputs: currentPrimeState, isPrimeCompleted, primeStartReqReceived, reservoirFilledStatus[] * @return none *************************************************************************/ void transitionToPrime( void ) { currentPrimeState = PRIME_START_STATE; currentReservoirMgmtState = PRIME_RESERVOIR_MGMT_START_STATE; isPrimeCompleted = FALSE; // TODO: set to false after integration with UI primeStartReqReceived = TRUE; reservoirFilledStatus[ DG_RESERVOIR_1 ] = FALSE; reservoirFilledStatus[ DG_RESERVOIR_2 ] = FALSE; } /*********************************************************************//** * @brief * The execPrime function executes the prime sub-mode state machine. * @details Inputs: currentPrimeState * @details Outputs: currentPrimeState * @return none *************************************************************************/ void execPrime( void ) { // execute prime sub-mode state machine switch ( currentPrimeState ) { case PRIME_START_STATE: cmdSetDGActiveReservoir( DG_RESERVOIR_2 ); currentPrimeState = PRIME_SALINE_SETUP_STATE; break; case PRIME_SALINE_SETUP_STATE: currentPrimeState = handlePrimeSalineSetupState(); break; case PRIME_SALINE_PURGE_AIR_STATE: currentPrimeState = handlePrimePurgeAirState(); break; case PRIME_SALINE_CIRC_BLOOD_CIRCUIT_STATE: currentPrimeState = handlePrimeCircBloodCircuitState(); break; case PRIME_RESERVOIR_ONE_FILL_COMPLETE_STATE: currentPrimeState = handlePrimeReservoirOneFillCompleteState(); break; case PRIME_DIALYSATE_DIALYZER_STATE: currentPrimeState = handlePrimeDialysateDialyzerState(); break; case PRIME_RESERVOIR_TWO_FILL_COMPLETE_STATE: currentPrimeState = handlePrimeReservoirTwoFillCompleteState(); break; case PRIME_DIALYSATE_BYPASS_STATE: currentPrimeState = handlePrimeDialysateBypassState(); break; default: currentPrimeState = PRIME_START_STATE; SET_ALARM_WITH_2_U32_DATA( ALARM_ID_HD_SOFTWARE_FAULT, SW_FAULT_ID_MODE_PRE_TREATMENT_PRIME_INVALID_STATE, (U32)currentReservoirMgmtState ); break; } execPreTreatmentReservoirMgmt(); } /*********************************************************************//** * @brief * The isPrimingPassed function returns the status of prime mode. * @details Inputs: none * @details Outputs: none * @return TRUE if prime has completed, otherwise FALSE *************************************************************************/ BOOL isPrimingPassed( void ) { return isPrimeCompleted; } /*********************************************************************//** * @brief * The execPreTreatmentReservoirMgmt function executes the state machine for the * reservoir management during pre-treatment mode. * @details Inputs: currentReservoirMgmtState * @details Outputs: DG reservoirs' fills managed. * @return none *************************************************************************/ static void execPreTreatmentReservoirMgmt( void ) { DG_OP_MODE_T dgOpMode = getDGOpMode(); U32 dgSubMode = getDGSubMode(); // treatment reservoir mgmt. state machine switch ( currentReservoirMgmtState ) { case PRIME_RESERVOIR_MGMT_START_STATE: if ( DG_MODE_CIRC == dgOpMode ) { currentReservoirMgmtState = PRIME_RESERVOIR_MGMT_FLUSH_DG_LINES_STATE; } else { cmdStartDG(); } break; case PRIME_RESERVOIR_MGMT_FLUSH_DG_LINES_STATE: if ( ( DG_MODE_CIRC == dgOpMode ) && ( DG_RECIRCULATE_MODE_STATE_RECIRC_WATER == dgSubMode ) ) { cmdStartDGDrain( PRIME_DRAIN_RESERVOIR_TO_VOLUME_ML, TRUE ); } if ( DG_MODE_DRAI == dgOpMode ) { currentReservoirMgmtState = PRIME_RESERVOIR_MGMT_DRAIN_STATE; } break; case PRIME_RESERVOIR_MGMT_DRAIN_STATE: if ( DG_MODE_CIRC == dgOpMode ) { currentReservoirMgmtState = PRIME_RESERVOIR_MGMT_START_FILL_STATE; } break; case PRIME_RESERVOIR_MGMT_START_FILL_STATE: if ( ( DG_MODE_CIRC == dgOpMode ) && ( DG_RECIRCULATE_MODE_STATE_RECIRC_WATER == dgSubMode ) ) { cmdStartDGFill( PRIME_FILL_RESERVOIR_TO_VOLUME_ML ); } if ( DG_MODE_FILL == dgOpMode ) { currentReservoirMgmtState = PRIME_RESERVOIR_MGMT_FILL_STATE; } break; case PRIME_RESERVOIR_MGMT_FILL_STATE: if ( DG_MODE_CIRC == dgOpMode ) { currentReservoirMgmtState = PRIME_RESERVOIR_MGMT_FILL_COMPLETE_STATE; } break; case PRIME_RESERVOIR_MGMT_FILL_COMPLETE_STATE: currentReservoirMgmtState = PRIME_RESERVOIR_MGMT_FILL_COMPLETE_STATE; if ( FALSE == reservoirFilledStatus[ DG_RESERVOIR_1 ] ) { reservoirFilledStatus[ DG_RESERVOIR_1 ] = TRUE; cmdSetDGActiveReservoir( DG_RESERVOIR_1 ); currentReservoirMgmtState = PRIME_RESERVOIR_MGMT_FLUSH_DG_LINES_STATE; } else if ( ( TRUE == reservoirFilledStatus[ DG_RESERVOIR_1 ] ) && ( FALSE == reservoirFilledStatus[ DG_RESERVOIR_2 ] ) ) { reservoirFilledStatus[ DG_RESERVOIR_2 ] = TRUE; } break; default: currentReservoirMgmtState = PRIME_RESERVOIR_MGMT_START_STATE; SET_ALARM_WITH_2_U32_DATA( ALARM_ID_HD_SOFTWARE_FAULT, SW_FAULT_ID_MODE_PRIME_RESERVOIR_MGMT_INVALID_STATE, (U32)currentReservoirMgmtState ); break; } } /*********************************************************************//** * @brief * The purgeAirValvesBloodPumpControl function controls valves and blood pump * to purge air. * @details Inputs: none * @details Outputs: run blood pump, close VDI, VDO, VBA and VBV valves, open VBT valve * @return current state (sub-mode) *************************************************************************/ static void purgeAirValvesBloodPumpControl( void ) { setValvePosition( VDI, VALVE_POSITION_C_CLOSE ); setValvePosition( VDO, VALVE_POSITION_C_CLOSE ); setValvePosition( VBA, VALVE_POSITION_C_CLOSE ); setValvePosition( VBV, VALVE_POSITION_C_CLOSE ); setValveAirTrap( STATE_OPEN ); setBloodPumpTargetFlowRate( BLOOD_PUMP_FLOW_RATE_PURGE_AIR, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); } /*********************************************************************//** * @brief * The handlePrimeSalineSetupState function checks user's request to start * priming. * @details Inputs: primeStartReqReceived * @details Outputs: control valves to purge air * @return current state *************************************************************************/ static PRE_TREATMENT_PRIME_STATE_T handlePrimeSalineSetupState( void ) { PRE_TREATMENT_PRIME_STATE_T state = PRIME_SALINE_SETUP_STATE; if ( TRUE == primeStartReqReceived ) { purgeAirValvesBloodPumpControl(); purgeAirTimeOutStartTime = getMSTimerCount(); state = PRIME_SALINE_PURGE_AIR_STATE; } return state; } /*********************************************************************//** * @brief * The handlePrimePurgeAirState function checks for air trap level and moves * to blood circuit circulation state if fluid is detected at upper sensor. * @details Inputs: air trap levels * @details Outputs: runs blood pump, control valves to trap air * @return current state *************************************************************************/ static PRE_TREATMENT_PRIME_STATE_T handlePrimePurgeAirState( void ) { PRE_TREATMENT_PRIME_STATE_T state = PRIME_SALINE_PURGE_AIR_STATE; if ( TRUE == didTimeout( purgeAirTimeOutStartTime, PURGE_AIR_TIME_OUT_COUNT ) ) { activateAlarmNoData( ALARM_ID_HD_PRIME_PURGE_AIR_TIME_OUT ); } if ( AIR_TRAP_LEVEL_FLUID == getAirTrapLevel( AIR_TRAP_LEVEL_SENSOR_UPPER ) ) { setValvePosition( VDI, VALVE_POSITION_C_CLOSE ); setValvePosition( VDO, VALVE_POSITION_C_CLOSE ); setValvePosition( VBA, VALVE_POSITION_B_OPEN ); setValvePosition( VBV, VALVE_POSITION_B_OPEN ); setValveAirTrap( STATE_CLOSED ); setBloodPumpTargetFlowRate( BLOOD_PUMP_FLOW_RATE_CIRC_BLOOD_CIRCUIT, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); noAirDetectedStartTime = getMSTimerCount(); state = PRIME_SALINE_CIRC_BLOOD_CIRCUIT_STATE; } return state; } /*********************************************************************//** * @brief * The handlePrimeCircBloodCircuitState function checks for air trap level and * return to purge air state if air is detected at lower sensor. If no air * detected for a period of time, the blood pump is stopped. * @details Inputs: air trap levels * @details Outputs: stop blood pump, control valves to purge air * @return current state *************************************************************************/ static PRE_TREATMENT_PRIME_STATE_T handlePrimeCircBloodCircuitState( void ) { PRE_TREATMENT_PRIME_STATE_T state = PRIME_SALINE_CIRC_BLOOD_CIRCUIT_STATE; if ( AIR_TRAP_LEVEL_AIR == getAirTrapLevel( AIR_TRAP_LEVEL_SENSOR_LOWER ) ) { purgeAirValvesBloodPumpControl(); purgeAirTimeOutStartTime = getMSTimerCount(); state = PRIME_SALINE_PURGE_AIR_STATE; } if ( TRUE == didTimeout( noAirDetectedStartTime, NO_AIR_DETECTED_COUNT ) ) { signalBloodPumpHardStop(); state = PRIME_RESERVOIR_ONE_FILL_COMPLETE_STATE; } return state; } /*********************************************************************//** * @brief * The handlePrimeReservoirOneFillCompleteState function waits for DG to finish * filling reservoir 1 before moving to priming dialyzer. * @details Inputs: reservoirFilledStatus[] * @details Outputs: update valves and pumps configuration on state change * @return current state *************************************************************************/ static PRE_TREATMENT_PRIME_STATE_T handlePrimeReservoirOneFillCompleteState( void ) { PRE_TREATMENT_PRIME_STATE_T state = PRIME_RESERVOIR_ONE_FILL_COMPLETE_STATE; if ( TRUE == reservoirFilledStatus[ DG_RESERVOIR_1 ] ) { setValvePosition( VDI, VALVE_POSITION_B_OPEN ); setValvePosition( VDO, VALVE_POSITION_B_OPEN ); setValvePosition( VBA, VALVE_POSITION_C_CLOSE ); setValvePosition( VBV, VALVE_POSITION_C_CLOSE ); setValveAirTrap( STATE_CLOSED ); setDialInPumpTargetFlowRate( DIALYSATE_PUMP_PRIME_FLOW_RATE, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); setDialOutPumpTargetRate( DIALYSATE_PUMP_PRIME_FLOW_RATE, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); previousLoadCellReading = 0; loadcellSteadyVolumeStartTime = getMSTimerCount(); primeDialysateDialyzerStartTime = getMSTimerCount(); state = PRIME_DIALYSATE_DIALYZER_STATE; } return state; } /*********************************************************************//** * @brief * The handlePrimeDialysateDialyzerState function handles priming for * dialysate dialyzer fluid path. * @details Inputs: reservoir 1 filtered weight * @details Outputs: primed dialysate dialyzer fluid path * @return current state *************************************************************************/ static PRE_TREATMENT_PRIME_STATE_T handlePrimeDialysateDialyzerState( void ) { PRE_TREATMENT_PRIME_STATE_T state = PRIME_DIALYSATE_DIALYZER_STATE; F32 const loadcellWeight = getReservoirWeightSmallFilter( LOAD_CELL_RESERVOIR_1_PRIMARY ); F32 const weightChange = fabs( 1.0 - ( previousLoadCellReading / loadcellWeight ) ); if ( weightChange < LOAD_CELL_VOLUME_NOISE_TOLERANCE ) { if ( TRUE == didTimeout( loadcellSteadyVolumeStartTime, MIN_LOAD_CELL_STEADY_VOLUME_TIME ) ) { state = PRIME_RESERVOIR_TWO_FILL_COMPLETE_STATE; } } else { previousLoadCellReading = loadcellWeight; loadcellSteadyVolumeStartTime = getMSTimerCount(); } if ( TRUE == didTimeout( primeDialysateDialyzerStartTime, PRIME_DIALYSATE_DIALYZER_TIME_LIMIT ) ) { SET_ALARM_WITH_1_U32_DATA( ALARM_ID_PRIME_DIALYSATE_DIALYZER_TIME_OUT, PRIME_DIALYSATE_DIALYZER_TIME_LIMIT ); } return state; } /*********************************************************************//** * @brief * The handlePrimeReservoirTwoFillCompleteState function waits for DG to finish * filling reservoir 2 before moving to pre-treatment re-circulation. * @details Inputs: reservoirFilledStatus[] * @details Outputs: update valves and pumps configuration on state change * @return current state *************************************************************************/ static PRE_TREATMENT_PRIME_STATE_T handlePrimeReservoirTwoFillCompleteState( void ) { PRE_TREATMENT_PRIME_STATE_T state = PRIME_DIALYSATE_DIALYZER_STATE; if ( TRUE == reservoirFilledStatus[ DG_RESERVOIR_2 ] ) { signalDialOutPumpHardStop(); setDialInPumpTargetFlowRate( DIALYSATE_PUMP_PRIME_FLOW_RATE, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); setValvePosition( VDI, VALVE_POSITION_C_CLOSE ); setValvePosition( VDO, VALVE_POSITION_C_CLOSE ); setValvePosition( VBA, VALVE_POSITION_C_CLOSE ); setValvePosition( VBV, VALVE_POSITION_C_CLOSE ); setValveAirTrap( STATE_CLOSED ); previousLoadCellReading = 0; primeDialysateBypassStartTime = getMSTimerCount(); loadcellSteadyVolumeStartTime = getMSTimerCount(); state = PRIME_DIALYSATE_BYPASS_STATE; } return state; } /*********************************************************************//** * @brief * The handlePrimeDialysateDialyzerState function handles priming for * dialysate dialyzer fluid path. * @details Inputs: reservoir 2 filtered weight * @details Outputs: primed dialysate bypass fluid path * @return current state *************************************************************************/ static PRE_TREATMENT_PRIME_STATE_T handlePrimeDialysateBypassState( void ) { F32 const loadcellWeight = getReservoirWeightSmallFilter( LOAD_CELL_RESERVOIR_2_PRIMARY ); F32 const weightChange = fabs( 1.0 - ( previousLoadCellReading / loadcellWeight ) ); if ( weightChange < LOAD_CELL_VOLUME_NOISE_TOLERANCE ) { isPrimeCompleted = didTimeout( loadcellSteadyVolumeStartTime, MIN_LOAD_CELL_STEADY_VOLUME_TIME ); } else { previousLoadCellReading = loadcellWeight; loadcellSteadyVolumeStartTime = getMSTimerCount(); } if ( TRUE == didTimeout( primeDialysateBypassStartTime, PRIME_DIALYSATE_BYPASS_TIME_LIMIT ) ) { SET_ALARM_WITH_1_U32_DATA( ALARM_ID_PRIME_DIALYSATE_BYPASS_TIME_OUT, PRIME_DIALYSATE_BYPASS_TIME_LIMIT ); } return PRIME_DIALYSATE_BYPASS_STATE; } /**@}*/