Index: firmware/App/Modes/Prime.c =================================================================== diff -u -r09e6cf9de34acf18f6e1138bf56ac0edb4821186 -rf1c099b79e0825afe54379577454518f7a134a73 --- firmware/App/Modes/Prime.c (.../Prime.c) (revision 09e6cf9de34acf18f6e1138bf56ac0edb4821186) +++ firmware/App/Modes/Prime.c (.../Prime.c) (revision f1c099b79e0825afe54379577454518f7a134a73) @@ -1,6 +1,6 @@ /************************************************************************** * -* Copyright (c) 2019-2021 Diality Inc. - All Rights Reserved. +* Copyright (c) 2019-2022 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. @@ -21,6 +21,7 @@ #include "DialInFlow.h" #include "DialOutFlow.h" #include "DGInterface.h" +#include "ModeTreatmentParams.h" #include "Prime.h" #include "SelfTests.h" #include "SystemCommMessages.h" @@ -35,21 +36,27 @@ // ********** private definitions ********** -#define MAX_PRIME_TIME ( 10 * SEC_PER_MIN ) ///< Maximum prime time (in seconds). -#define PRIME_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Interval (ms/task time) at which the prime data is published on the CAN bus. +#define MAX_PRIME_TIME ( 10 * SEC_PER_MIN ) ///< Maximum prime time (in seconds). +#define PRIME_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Interval (ms/task time) at which the prime data is published on the CAN bus. -#define BLOOD_PUMP_FAST_FLOW_RATE_PURGE_AIR_ML_MIN 400 ///< Blood pump fast flow rate to fill fluid. -#define BLOOD_PUMP_SLOW_FLOW_RATE_PURGE_AIR_ML_MIN 150 ///< Blood pump slow flow rate after fluid reach lower level of air trap sensor. -#define BLOOD_PUMP_FLOW_RATE_CIRC_BLOOD_CIRCUIT_ML_MIN 300 ///< Blood pump flow rate during prime recirculate blood circuit state. +#define BLOOD_PUMP_FAST_FLOW_RATE_PURGE_AIR_ML_MIN 300 ///< Blood pump fast flow rate to fill fluid. +#define BLOOD_PUMP_SLOW_FLOW_RATE_PURGE_AIR_ML_MIN 150 ///< Blood pump slow flow rate after fluid reach lower level of air trap sensor. +#define BLOOD_PUMP_SALINE_FLOW_RATE_PURGE_AIR_ML_MIN 200 ///< Blood pump very slow flow rate during prime saline dialyzer state +#define BLOOD_PUMP_FLOW_RATE_CIRC_BLOOD_CIRCUIT_ML_MIN 300 ///< Blood pump flow rate during prime recirculate blood circuit state. +#define BLOOD_PUMP_FLOW_RATE_SALINE_DIALYZER_ML_MIN 300 ///< Blood pump flow rate during prime the saline dialyzer dialysate state. +#define DIALYSATE_PUMP_PRIME_FLOW_RATE_ML_MIN 300 ///< Dialysate pump flow rate during priming fluid path. -#define DIALYSATE_PUMP_PRIME_FLOW_RATE_ML_MIN 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 DIALYSATE_DIALYZER_TUBE_VOLUME_ML 115 ///< This total tube volume is used to calculate the Dpi & Dpo time out in the dialysate dialyzer state. +#define DIALYSATE_DIALYZER_BYPASS_TUBE_VOLUME_ML 75 ///< This volume is used to calculate the DPi pump time out in the dialyzer bypass state. +#define DIALYZER_DVI_PATH_VOLUME_ML 17 ///< Path volume from the dialyzer to the VDI valve in mL. +#define DIALYZER_VOLUME_SCALE_FACTOR 0.5 ///< Half of the dialyzer total volume. -#define NO_AIR_DETECTED_COUNT ( 20 * MS_PER_SECOND ) ///< No air detected time period count. -#define PURGE_AIR_TIME_OUT_COUNT ( 60 * 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. +#define NO_AIR_DETECTED_COUNT ( 20 * MS_PER_SECOND ) ///< No air detected time period count. +#define PURGE_AIR_TIME_OUT_COUNT ( 60 * MS_PER_SECOND ) ///< Time period count for purge air time out. +#define LOAD_CELL_STEADY_VOLUME_SAMPLING_TIME ( 1 * MS_PER_SECOND ) ///< Time load cell reading steady state detection sampling time in seconds. +#define PRIME_DIALYSATE_BYPASS_TIME_LIMIT ( 15 * MS_PER_SECOND ) ///< Time limit for priming dialysate bypass circuit. +#define STEADY_VOLUME_COUNT_SEC ( 10000 / LOAD_CELL_STEADY_VOLUME_SAMPLING_TIME ) ///< Counter must be greater than 10 seconds before steady volume is true. +#define STEADY_VOLUME_TIME_DEADLINE_MS ( 55 * MS_PER_SECOND ) ///< Time in msec for the steady volume deadline time out. /// States of the treatment reservoir management state machine. typedef enum PrimeReservoirMgmt_States @@ -66,8 +73,22 @@ NUM_OF_PRIME_RESERVOIR_MGMT_STATES ///< Number of prime reservoir mgmt. states. } PRIME_RESERVOIR_MGMT_STATE_T; +typedef struct { + U32 bloodVolume; ///< Blood volume of the dialyzer in mL. + U32 dialysateVolume; ///< Dialysate volume of the dialyzer in mL. +} DIALYZER_VOLUME_DATA_T; + // ********** private data ********** +static DIALYZER_VOLUME_DATA_T dialyzerVolumeTable[ ] = { { 82, 170 }, ///< Dialyzer volume table in mL of five types of dialyzer + { 100, 200 }, + { 120, 257 }, + { 87, 233 }, + { 102, 280 } }; + +static U32 primeDialysateDialyzerTimeLimit; ///< Time limit in msec for priming dialysate dialyzer path. +static U32 primeSalineDialyzerTimeLimit; ///< Time limit in msec for priming saline dialyzer path. + static HD_PRE_TREATMENT_PRIME_STATE_T currentPrimeState; ///< Current state of the prime sub-mode state machine. static HD_PRE_TREATMENT_PRIME_STATE_T previousPrimeState; ///< Previous state of the prime sub-mode, to use when resuming from pause. static PRIME_RESERVOIR_MGMT_STATE_T currentReservoirMgmtState; ///< Current reservoir management state. @@ -81,20 +102,25 @@ static U32 noAirDetectedStartTime; ///< starting time when detecting no air. static U32 purgeAirTimeOutStartTime; ///< Starting time for purge air state time out. +static U32 primeSalineDialyzerStartTime; ///< Starting time of priming saline dialyzer circuit. 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. -static BOOL runBloodCircuitPrimeAgain; ///< Flag indicates HD should run blood circuit prime once more time. +static U32 steadyVolumeSamplingStartTime; ///< Load cell steady volume sampling interval starting time. +static F32 minimumReservoirVolume; ///< Minimum reservoir volume in mL. +static U32 steadyVolumeCount; ///< Use to keep track the number of dVolume/dt < Threshold // ********** private function prototypes ********** static void resetPrimeFlags(); static void setupForPrimePause( void ); static void broadcastPrimingStatus( void ); static void purgeAirValvesBloodPumpControl( void ); +static F32 getDialyzerBloodVolume( void ); +static F32 getDialyzerDialysateVolume( void ); static HD_PRE_TREATMENT_PRIME_STATE_T handlePrimeWaitForUserStartState( void ); +static HD_PRE_TREATMENT_PRIME_STATE_T handlePrimeSalineDialyzerSetupState( void ); +static HD_PRE_TREATMENT_PRIME_STATE_T handlePrimeSalineDialyzerState( void ); static HD_PRE_TREATMENT_PRIME_STATE_T handlePrimeSalineSetupState( void ); static HD_PRE_TREATMENT_PRIME_STATE_T handlePrimePurgeAirState( void ); static HD_PRE_TREATMENT_PRIME_STATE_T handlePrimeCircBloodCircuitState( void ); @@ -134,7 +160,6 @@ primeStartTime = getMSTimerCount(); primePauseStartTime = 0; primeStatusBroadcastTimerCounter = 0; - runBloodCircuitPrimeAgain = TRUE; setAlarmUserActionEnabled( ALARM_USER_ACTION_RESUME, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_RINSEBACK, FALSE ); @@ -187,6 +212,14 @@ currentPrimeState = handlePrimeDialysateDialyzerState(); break; + case HD_PRIME_SALINE_DIALYZER_SETUP_STATE: + currentPrimeState = handlePrimeSalineDialyzerSetupState(); + break; + + case HD_PRIME_SALINE_DIALYZER_STATE: + currentPrimeState = handlePrimeSalineDialyzerState(); + break; + case HD_PRIME_RESERVOIR_TWO_FILL_COMPLETE_STATE: currentPrimeState = handlePrimeReservoirTwoFillCompleteState(); break; @@ -232,6 +265,36 @@ /*********************************************************************//** * @brief + * The getDialyzerBloodVolume function returns the blood volume of + * the selected dialyzer. + * @details Inputs: dialyzer volume table + * @details Outputs: none + * @return blood volume capacity of the dialyzer in mL + *************************************************************************/ +F32 getDialyzerBloodVolume( void ) +{ + U32 dialyzerType = getTreatmentParameterU32( TREATMENT_PARAM_DIALYZER_TYPE ); + + return dialyzerVolumeTable[ dialyzerType ].bloodVolume; +} + +/*********************************************************************//** + * @brief + * The getDialyzerDialysateVolume function returns the dialysate volume + * of the selected dialyzer. + * @details Inputs: dialyzer volume table + * @details Outputs: none + * @return dialysate volume capacity of the dialyzer in mL + *************************************************************************/ +F32 getDialyzerDialysateVolume( void ) +{ + U32 dialyzerType = getTreatmentParameterU32( TREATMENT_PARAM_DIALYZER_TYPE ); + + return dialyzerVolumeTable[ dialyzerType ].dialysateVolume; +} + +/*********************************************************************//** + * @brief * The signalStartPrime function signals the prime sub-mode the user requested * to start priming operation. * @details Inputs: none @@ -374,7 +437,8 @@ /*********************************************************************//** * @brief - * The handlePrimeSalineSetupState function checks user's request to start + * The handlePrimeSalineSetupState function prepares the entry conditions + * before start priming the saline dialyzer fluid path. * priming. * @details Inputs: primeStartReqReceived * @details Outputs: control valves to purge air @@ -493,17 +557,23 @@ if ( TRUE == hasDGCompletedReservoirSwitch() ) { + U32 dialyzerDialysateVolume = getDialyzerDialysateVolume(); + 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 ); + signalBloodPumpHardStop(); setDialInPumpTargetFlowRate( DIALYSATE_PUMP_PRIME_FLOW_RATE_ML_MIN, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); setDialOutPumpTargetRate( DIALYSATE_PUMP_PRIME_FLOW_RATE_ML_MIN, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); - previousLoadCellReading = 0; - loadcellSteadyVolumeStartTime = getMSTimerCount(); + // Calculate the time out value that must passed prior to checking for the steady state volume in the reservoir + primeDialysateDialyzerTimeLimit = (U32)( ( ( DIALYSATE_DIALYZER_TUBE_VOLUME_ML + dialyzerDialysateVolume ) * SEC_PER_MIN * MS_PER_SECOND ) / DIALYSATE_PUMP_PRIME_FLOW_RATE_ML_MIN ); + minimumReservoirVolume = getLoadCellWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); + steadyVolumeCount = 0; + steadyVolumeSamplingStartTime = getMSTimerCount(); primeDialysateDialyzerStartTime = getMSTimerCount(); #ifdef SKIP_PRIMING @@ -535,33 +605,37 @@ { HD_PRE_TREATMENT_PRIME_STATE_T state = HD_PRIME_DIALYSATE_DIALYZER_STATE; - F32 const loadcellWeight = getLoadCellWeight( LOAD_CELL_RESERVOIR_1_PRIMARY ); - F32 const weightChange = fabs( 1.0 - ( previousLoadCellReading / loadcellWeight ) ); - - if ( weightChange < LOAD_CELL_VOLUME_NOISE_TOLERANCE ) + if ( TRUE == didTimeout( primeDialysateDialyzerStartTime, primeDialysateDialyzerTimeLimit ) ) { - if ( TRUE == didTimeout( loadcellSteadyVolumeStartTime, MIN_LOAD_CELL_STEADY_VOLUME_TIME ) ) + // check for volume steady state every second after the DVi and DVo pumps have been on for primeDialysateDialyzerTimeLimit seconds + if ( TRUE == didTimeout( steadyVolumeSamplingStartTime, LOAD_CELL_STEADY_VOLUME_SAMPLING_TIME ) ) { - if ( TRUE == runBloodCircuitPrimeAgain ) + F32 const currentReservoirVolume = getLoadCellWeight( LOAD_CELL_RESERVOIR_1_PRIMARY ); + + if ( currentReservoirVolume >= minimumReservoirVolume ) { - runBloodCircuitPrimeAgain = FALSE; - state = HD_PRIME_SALINE_SETUP_STATE; + if ( ++steadyVolumeCount >= STEADY_VOLUME_COUNT_SEC ) + { + state = HD_PRIME_SALINE_DIALYZER_SETUP_STATE; + } } else { - state = HD_PRIME_RESERVOIR_TWO_FILL_COMPLETE_STATE; + minimumReservoirVolume = currentReservoirVolume; + steadyVolumeCount = 0; // required 10 seconds continuous steady volume to transition to next state } + + steadyVolumeSamplingStartTime = getMSTimerCount(); // re-armed the timer for the next dVolume/dt check } } else { - previousLoadCellReading = loadcellWeight; - loadcellSteadyVolumeStartTime = getMSTimerCount(); + steadyVolumeSamplingStartTime = getMSTimerCount(); } - if ( TRUE == didTimeout( primeDialysateDialyzerStartTime, PRIME_DIALYSATE_DIALYZER_TIME_LIMIT ) ) + if ( TRUE == didTimeout( primeDialysateDialyzerStartTime, primeDialysateDialyzerTimeLimit + STEADY_VOLUME_TIME_DEADLINE_MS ) ) { - SET_ALARM_WITH_1_U32_DATA( ALARM_ID_PRIME_DIALYSATE_DIALYZER_TIME_OUT, PRIME_DIALYSATE_DIALYZER_TIME_LIMIT ); + SET_ALARM_WITH_1_U32_DATA( ALARM_ID_PRIME_DIALYSATE_DIALYZER_TIME_OUT, primeDialysateDialyzerTimeLimit ); } if ( TRUE == doesAlarmStatusIndicateStop() ) @@ -575,6 +649,67 @@ /*********************************************************************//** * @brief + * The handlePrimeSalineDialyzerSetupState function implements the setup + * prior to transition to the prime the saline dialyzer state. + * @details Inputs: primeStartReqReceived + * @details Outputs: primeSalineDializerInitialLoadCellReading, primeSalineDialyzerStartTime + * @return current state + *************************************************************************/ +static HD_PRE_TREATMENT_PRIME_STATE_T handlePrimeSalineDialyzerSetupState( void ) +{ + HD_PRE_TREATMENT_PRIME_STATE_T state = HD_PRIME_SALINE_DIALYZER_STATE; + U32 dialyzerDialysateVolume = getDialyzerDialysateVolume(); + + setValvePosition( VDI, VALVE_POSITION_A_INSERT_EJECT ); + setValvePosition( VDO, VALVE_POSITION_C_CLOSE ); + setValvePosition( VBA, VALVE_POSITION_C_CLOSE ); + setValvePosition( VBV, VALVE_POSITION_C_CLOSE ); + setValveAirTrap( STATE_CLOSED ); + + signalDialOutPumpHardStop(); + signalDialInPumpHardStop(); + setBloodPumpTargetFlowRate( BLOOD_PUMP_FLOW_RATE_SALINE_DIALYZER_ML_MIN, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); + + primeSalineDialyzerTimeLimit = (U32)( ( ( dialyzerDialysateVolume * DIALYZER_VOLUME_SCALE_FACTOR + DIALYZER_DVI_PATH_VOLUME_ML ) * SEC_PER_MIN * MS_PER_SECOND ) / BLOOD_PUMP_FLOW_RATE_SALINE_DIALYZER_ML_MIN ); + primeSalineDialyzerStartTime = getMSTimerCount(); + + if ( TRUE == doesAlarmStatusIndicateStop() ) + { + setupForPrimePause(); + state = HD_PRIME_PAUSE; + } + + return state; +} + +/*********************************************************************//** + * @brief + * The handlePrimeSalineDialyzerState function handles priming for saline + * dialyzer fluid path. + * @details Inputs: reservoir 2 filtered weight + * @details Outputs: primed saline dialyzer fluid path + * @return current state + *************************************************************************/ +static HD_PRE_TREATMENT_PRIME_STATE_T handlePrimeSalineDialyzerState( void ) +{ + HD_PRE_TREATMENT_PRIME_STATE_T state = HD_PRIME_SALINE_DIALYZER_STATE; + + if ( TRUE == didTimeout( primeSalineDialyzerStartTime, primeSalineDialyzerTimeLimit ) ) + { + state = HD_PRIME_RESERVOIR_TWO_FILL_COMPLETE_STATE; + } + + if ( TRUE == doesAlarmStatusIndicateStop() ) + { + setupForPrimePause(); + state = HD_PRIME_PAUSE; + } + + return state; +} + +/*********************************************************************//** + * @brief * The handlePrimeReservoirTwoFillCompleteState function waits for DG to finish * filling reservoir 2 before moving to pre-treatment re-circulation. * @details Inputs: reservoirFilledStatus[] @@ -591,7 +726,7 @@ if ( TRUE == hasDGCompletedReservoirSwitch() ) { - signalDialOutPumpHardStop(); + signalBloodPumpHardStop(); setDialInPumpTargetFlowRate( DIALYSATE_PUMP_PRIME_FLOW_RATE_ML_MIN, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); setValvePosition( VDI, VALVE_POSITION_C_CLOSE ); @@ -600,9 +735,10 @@ setValvePosition( VBV, VALVE_POSITION_C_CLOSE ); setValveAirTrap( STATE_CLOSED ); - previousLoadCellReading = 0; + minimumReservoirVolume = getLoadCellWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); + steadyVolumeCount = 0; primeDialysateBypassStartTime = getMSTimerCount(); - loadcellSteadyVolumeStartTime = getMSTimerCount(); + steadyVolumeSamplingStartTime = getMSTimerCount(); state = HD_PRIME_DIALYSATE_BYPASS_STATE; } } @@ -618,33 +754,46 @@ /*********************************************************************//** * @brief - * The handlePrimeDialysateDialyzerState function handles priming for - * dialysate dialyzer fluid path. + * The handlePrimeDialysateBypassState function handles priming for + * dialysate dialyzer bypass fluid path. * @details Inputs: reservoir 2 filtered weight * @details Outputs: primed dialysate bypass fluid path * @return current state *************************************************************************/ static HD_PRE_TREATMENT_PRIME_STATE_T handlePrimeDialysateBypassState( void ) { HD_PRE_TREATMENT_PRIME_STATE_T state = HD_PRIME_DIALYSATE_BYPASS_STATE; - F32 const loadcellWeight = getLoadCellWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); - F32 const weightChange = fabs( 1.0 - ( previousLoadCellReading / loadcellWeight ) ); - if ( weightChange < LOAD_CELL_VOLUME_NOISE_TOLERANCE ) + if ( TRUE == didTimeout( primeDialysateBypassStartTime, PRIME_DIALYSATE_BYPASS_TIME_LIMIT ) ) { - if ( TRUE == didTimeout( loadcellSteadyVolumeStartTime, MIN_LOAD_CELL_STEADY_VOLUME_TIME ) ) + // check for steady volume every second after the DVi pump has been on for PRIME_DIALYSATE_BYPASS_TIME_LIMIT seconds + if ( TRUE == didTimeout( steadyVolumeSamplingStartTime, LOAD_CELL_STEADY_VOLUME_SAMPLING_TIME ) ) { - transitionToWetSelfTests(); - state = HD_PRIME_WET_SELF_TESTS_STATE; + F32 const currentReservoirVolume = getLoadCellWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); + + if ( currentReservoirVolume >= minimumReservoirVolume ) + { + if ( ++steadyVolumeCount >= STEADY_VOLUME_COUNT_SEC ) + { + transitionToWetSelfTests(); + state = HD_PRIME_WET_SELF_TESTS_STATE; + } + } + else + { + minimumReservoirVolume = currentReservoirVolume; + steadyVolumeCount = 0; + } + + steadyVolumeSamplingStartTime = getMSTimerCount(); // re-armed the timer for the next 1 second iteration } } else { - previousLoadCellReading = loadcellWeight; - loadcellSteadyVolumeStartTime = getMSTimerCount(); + steadyVolumeSamplingStartTime = getMSTimerCount(); } - if ( TRUE == didTimeout( primeDialysateBypassStartTime, PRIME_DIALYSATE_BYPASS_TIME_LIMIT ) ) + if ( TRUE == didTimeout( primeDialysateBypassStartTime, PRIME_DIALYSATE_BYPASS_TIME_LIMIT + STEADY_VOLUME_TIME_DEADLINE_MS ) ) // allocate 55 seconds for the steady state logic to do its job before timing out { SET_ALARM_WITH_1_U32_DATA( ALARM_ID_PRIME_DIALYSATE_BYPASS_TIME_OUT, PRIME_DIALYSATE_BYPASS_TIME_LIMIT ); } @@ -722,6 +871,11 @@ state = HD_PRIME_RESERVOIR_ONE_FILL_COMPLETE_STATE; break; + case HD_PRIME_SALINE_DIALYZER_SETUP_STATE: + case HD_PRIME_SALINE_DIALYZER_STATE: + state = HD_PRIME_SALINE_DIALYZER_SETUP_STATE; + break; + case HD_PRIME_RESERVOIR_TWO_FILL_COMPLETE_STATE: case HD_PRIME_DIALYSATE_BYPASS_STATE: state = HD_PRIME_RESERVOIR_TWO_FILL_COMPLETE_STATE;