/************************************************************************** * * Copyright (c) 2019-2021 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 BloodPrime.c * * @author (last) Sean Nash * @date (last) 13-Aug-2021 * * @author (original) Sean Nash * @date (original) 06-Feb-2021 * ***************************************************************************/ #include "AirTrap.h" #include "BloodFlow.h" #include "BloodPrime.h" #include "DialInFlow.h" #include "DialOutFlow.h" #include "ModeTreatment.h" #include "ModeTreatmentParams.h" #include "OperationModes.h" #include "SyringePump.h" #include "SystemCommMessages.h" #include "TaskGeneral.h" #include "Utilities.h" #include "Valves.h" /** * @addtogroup BloodPrime * @{ */ // ********** private definitiions *********** // TODO - get from Systems when available #define TARGET_BLOOD_PRIME_VOLUME_ML 300.0 ///< Target blood prime volume to prime the blood side circuit (in mL). #define MIN_RAMP_TIME_SEC 60 ///< Minimum ramp time for blood prime (in seconds). #ifndef DISABLE_PUMP_FLOW_CHECKS /// Maximum blood prime volume measured by independent means (as % of target). static const F32 MAX_BLOOD_PRIME_SAFETY_VOLUME_ML = ( TARGET_BLOOD_PRIME_VOLUME_ML * 1.2 ); /// Minimum blood prime volume measured by independent means (as % of target). static const F32 MIN_BLOOD_PRIME_SAFETY_VOLUME_ML = ( TARGET_BLOOD_PRIME_VOLUME_ML * 0.8 ); #endif /// Initial flow rate for blood pump when starting blood prime operation. #define BLOOD_PRIME_INIT_BP_FLOW_RATE_ML_MIN 100 /// Interval at which blood prime ramping is controlled. static const U32 BLOOD_PRIME_RAMPING_INTERVAL = ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ); /// Interval at which blood prime progress is to be published to UI. #define BLOOD_PRIME_DATA_PUBLISH_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) /// Multiplier to convert flow (mL/min) into volume (mL) for period of general task interval. static const F32 BLOOD_PRIME_FLOW_INTEGRATOR = 1.0 / (F32)( SEC_PER_MIN * ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ); // ********** private data ********** static BLOOD_PRIME_STATE_T bloodPrimeState; ///< Current state of the blood prime sub-mode. static F32 bloodPrimeRampFlowRate_mL_min; ///< Current blood pump ramp flow rate. static F32 bloodPrimeRampStep_mL; ///< Blood pump volume step size for ramping. static U32 bloodPrimeRampControlTimerCtr; ///< Timer counter for determining interval for controlling BP ramp. static U32 bloodPrimePublishTimerCtr; ///< Timer counter for determining interval for blood prime status to be published. /// Interval (in task intervals) at which to publish blood prime data to CAN bus. static OVERRIDE_U32_T bloodPrimePublishInterval = { BLOOD_PRIME_DATA_PUBLISH_INTERVAL, BLOOD_PRIME_DATA_PUBLISH_INTERVAL, BLOOD_PRIME_DATA_PUBLISH_INTERVAL, 0 }; static OVERRIDE_F32_T cumulativeBloodPrimeVolume_mL = { 0.0, 0.0, 0.0, 0 }; ///< Total cumulative blood prime volume (in mL). static OVERRIDE_F32_T bloodPrimeVolumeDelivered_Safety = { 0.0, 0.0, 0.0, 0 }; ///< The cumulative independent blood prime volume (in mL) calculated so far. static S32 bloodPrimeMotorCount; ///< The cumulative sum of BP motor encoder counts used for independent blood prime volume check. static U32 bloodPrimeLastMotorCount; ///< The last BP motor encoder count read for independent blood prime volume check. // ********** private function prototypes ********** static void resetBloodPrimeFlags( void ); static F32 getBloodPrimeVolume( void ); static F32 getBloodPrimeSafetyVolume( void ); static BLOOD_PRIME_STATE_T handleBloodPrimeRampState( void ); static void publishBloodPrimeData( void ); /*********************************************************************//** * @brief * The initBloodPrime function initializes the Blood Prime sub-mode * module. * @details Inputs: none * @details Outputs: Blood prime sub-mode module initialized. * @return none *************************************************************************/ void initBloodPrime( void ) { U32 setBPRate = getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ); bloodPrimeState = BLOOD_PRIME_RAMP_STATE; bloodPrimeRampControlTimerCtr = 0; bloodPrimePublishTimerCtr = 0; cumulativeBloodPrimeVolume_mL.data = 0.0; bloodPrimeVolumeDelivered_Safety.data = 0.0; bloodPrimeMotorCount = 0; bloodPrimeLastMotorCount = getBloodPumpMotorCount(); resetBloodPrimeFlags(); bloodPrimeRampFlowRate_mL_min = (F32)BLOOD_PRIME_INIT_BP_FLOW_RATE_ML_MIN; // Calculate BP ramp step size if ( setBPRate <= BLOOD_PRIME_INIT_BP_FLOW_RATE_ML_MIN ) { bloodPrimeRampStep_mL = 0.0; } else { F32 rampRateSpan = (F32)( setBPRate - BLOOD_PRIME_INIT_BP_FLOW_RATE_ML_MIN ); F32 estRampSeconds = ( ( TARGET_BLOOD_PRIME_VOLUME_ML / (F32)setBPRate + TARGET_BLOOD_PRIME_VOLUME_ML / (F32)BLOOD_PRIME_INIT_BP_FLOW_RATE_ML_MIN ) / 2.0 ) * (F32)SEC_PER_MIN; if ( estRampSeconds < (F32)MIN_RAMP_TIME_SEC ) { estRampSeconds = (F32)MIN_RAMP_TIME_SEC; } bloodPrimeRampStep_mL = rampRateSpan / estRampSeconds; } } /*********************************************************************//** * @brief * The transitionToBloodPrime function prepares for transition to blood prime * sub-mode. * @details Inputs: none * @details Outputs: actuators set for initial state of blood prime sub-mode * @return none *************************************************************************/ void transitionToBloodPrime( void ) { initBloodPrime(); // Set valves setValvePosition( VDI, VALVE_POSITION_C_CLOSE ); setValvePosition( VDO, VALVE_POSITION_C_CLOSE ); setValvePosition( VBA, VALVE_POSITION_B_OPEN ); setValvePosition( VBV, VALVE_POSITION_B_OPEN ); // Ensure dialysate outlet and Heparin pumps are stopped signalDialOutPumpHardStop(); stopSyringePump(); // start blood and dialysate inlet pumps setBloodPumpTargetFlowRate( BLOOD_PRIME_INIT_BP_FLOW_RATE_ML_MIN, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); setDialInPumpTargetFlowRate( DIALYSATE_FLOW_RATE_FOR_RECIRC, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_CLOSED_LOOP ); cmdStartDGTrimmerHeater(); // Start air trap control startAirTrapControl(); // Set user alarm recovery actions allowed in this sub-mode setAlarmUserActionEnabled( ALARM_USER_ACTION_RESUME, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_RINSEBACK, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_END_TREATMENT, TRUE ); } /*********************************************************************//** * @brief * The resetBloodPrimeFlags function resets the blood prime request flags. * @details Inputs: none * @details Outputs: Blood prime request flags reset to FALSE. * @return none *************************************************************************/ static void resetBloodPrimeFlags( void ) { // No flags for now } /*********************************************************************//** * @brief * The getBloodPrimeVolume function gets the calculated blood prime volume * delivered. * @details Inputs: cumulativeBloodPrimeVolume_mL * @details Outputs: none * @return the current blood prime volume delivered (in mL). *************************************************************************/ static F32 getBloodPrimeVolume( void ) { F32 result = cumulativeBloodPrimeVolume_mL.data; if ( OVERRIDE_KEY == cumulativeBloodPrimeVolume_mL.override ) { result = cumulativeBloodPrimeVolume_mL.ovData; } return result; } /*********************************************************************//** * @brief * The getBloodPrimeSafetyVolume function gets the calculated independent * blood prime volume delivered. * @details Inputs: bloodPrimeVolumeDelivered_Safety * @details Outputs: none * @return the current blood prime safety volume delivered (in mL). *************************************************************************/ static F32 getBloodPrimeSafetyVolume( void ) { F32 result = bloodPrimeVolumeDelivered_Safety.data; if ( OVERRIDE_KEY == bloodPrimeVolumeDelivered_Safety.override ) { result = bloodPrimeVolumeDelivered_Safety.ovData; } return result; } /*********************************************************************//** * @brief * The execBloodPrime function executes the Blood Prime sub-mode state machine. * @details Inputs: bloodPrimeState * @details Outputs: bloodPrimeState * @return none *************************************************************************/ void execBloodPrime( void ) { switch ( bloodPrimeState ) { case BLOOD_PRIME_RAMP_STATE: bloodPrimeState = handleBloodPrimeRampState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_HD_SOFTWARE_FAULT, SW_FAULT_ID_BLOOD_PRIME_INVALID_STATE, bloodPrimeState ); break; } // Blood prime flags should be handled by now - reset in case not handled by current state resetBloodPrimeFlags(); // Publish blood prime progress while in blood prime sub-mode publishBloodPrimeData(); } /*********************************************************************//** * @brief * The handleBloodPrimeRampState function handles the blood prime ramp * state operations. * @details Inputs: flags * @details Outputs: flags handled * @return next blood prime state *************************************************************************/ static BLOOD_PRIME_STATE_T handleBloodPrimeRampState( void ) { BLOOD_PRIME_STATE_T result = BLOOD_PRIME_RAMP_STATE; // Update blood prime volume delivered so far cumulativeBloodPrimeVolume_mL.data += ( getMeasuredBloodFlowRate() * BLOOD_PRIME_FLOW_INTEGRATOR ); // Update independent calculated safety volume delivered so far bloodPrimeMotorCount = u32BiDiffWithWrap( bloodPrimeLastMotorCount, getBloodPumpMotorCount() ) / BP_HALL_EDGE_COUNTS_PER_REV; bloodPrimeVolumeDelivered_Safety.data = ( (F32)bloodPrimeMotorCount * VOLUME_PER_BP_MOTOR_REV_ML ); // TODO - include upstream pressure compensation to this calc // Has blood prime completed? if ( getBloodPrimeVolume() >= TARGET_BLOOD_PRIME_VOLUME_ML ) { #ifndef DISABLE_PUMP_FLOW_CHECKS // check for under-delivery if ( getBloodPrimeSafetyVolume() < MIN_BLOOD_PRIME_SAFETY_VOLUME_ML ) { SET_ALARM_WITH_2_F32_DATA( ALARM_ID_BLOOD_PRIME_VOLUME_CHECK_FAILURE, TARGET_BLOOD_PRIME_VOLUME_ML, getBloodPrimeSafetyVolume() ); } else #endif { setBloodIsPrimed( TRUE ); signalDialInPumpHardStop(); signalBloodPrimeToDialysis(); cmdStopDGTrimmerHeater(); } } #ifndef DISABLE_PUMP_FLOW_CHECKS // Has independent safety volume exceeded safety limit? else if ( getBloodPrimeSafetyVolume() > MAX_BLOOD_PRIME_SAFETY_VOLUME_ML ) { SET_ALARM_WITH_2_F32_DATA( ALARM_ID_BLOOD_PRIME_VOLUME_CHECK_FAILURE, TARGET_BLOOD_PRIME_VOLUME_ML, getBloodPrimeSafetyVolume() ); } #endif else { // ramp blood pump on ramp interval if ( ++bloodPrimeRampControlTimerCtr >= BLOOD_PRIME_RAMPING_INTERVAL ) { U32 setBPRate = getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ); bloodPrimeRampControlTimerCtr = 0; if ( bloodPrimeRampFlowRate_mL_min < (F32)setBPRate ) { bloodPrimeRampFlowRate_mL_min += bloodPrimeRampStep_mL; setBloodPumpTargetFlowRate( (U32)bloodPrimeRampFlowRate_mL_min, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); } } } return result; } /*********************************************************************//** * @brief * The getCurrentBloodPrimeState function returns the current state of the * blood prime sub-mode. * @details Inputs: bloodPrimeState * @details Outputs: none * @return bloodPrimeState *************************************************************************/ BLOOD_PRIME_STATE_T getCurrentBloodPrimeState( void ) { return bloodPrimeState; } /*********************************************************************//** * @brief * The publishBloodPrimeData function publishes blood prime progress to UI * at 1 Hz interval. * @details Inputs: bloodPrimePublishTimerCtr * @details Outputs: rinseback data published * @return none *************************************************************************/ static void publishBloodPrimeData( void ) { if ( ++bloodPrimePublishTimerCtr >= getU32OverrideValue( &bloodPrimePublishInterval ) ) { BLOOD_PRIME_DATA_PAYLOAD_T data; bloodPrimePublishTimerCtr = 0; data.targetBloodPrimeVolumeMl = TARGET_BLOOD_PRIME_VOLUME_ML; data.deliveredBloodPrimeVolumeMl = getBloodPrimeVolume(); data.indDeliveredBloodPrimeVolumeMl = getBloodPrimeSafetyVolume(); broadcastData( MSG_ID_HD_BLOOD_PRIME_PROGRESS, COMM_BUFFER_OUT_CAN_HD_BROADCAST, (U08*)&data, sizeof( BLOOD_PRIME_DATA_PAYLOAD_T ) ); } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testSetBloodPrimeVolumeOverride function overrides the calculated * blood prime volume. * @details Inputs: none * @details Outputs: cumulativeBloodPrimeVolume_mL * @param vol override calculated blood prime volume (in mL) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetBloodPrimeVolumeOverride( F32 vol ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; cumulativeBloodPrimeVolume_mL.ovData = vol; cumulativeBloodPrimeVolume_mL.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetBloodPrimeVolumeOverride function resets the override of the * calculated blood prime volume. * @details Inputs: none * @details Outputs: cumulativeBloodPrimeVolume_mL * @return TRUE if reset successful, FALSE if not *************************************************************************/ BOOL testResetBloodPrimeVolumeOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; cumulativeBloodPrimeVolume_mL.override = OVERRIDE_RESET; cumulativeBloodPrimeVolume_mL.ovData = cumulativeBloodPrimeVolume_mL.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetBloodPrimeSafetyVolumeOverride function overrides the calculated * blood prime volume. * @details Inputs: none * @details Outputs: bloodPrimeVolumeDelivered_Safety * @param vol override calculated blood prime safety volume (in mL) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetBloodPrimeSafetyVolumeOverride( F32 vol ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; bloodPrimeVolumeDelivered_Safety.ovData = vol; bloodPrimeVolumeDelivered_Safety.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetBloodPrimeSafetyVolumeOverride function resets the override of the * calculated blood prime safety volume. * @details Inputs: none * @details Outputs: bloodPrimeVolumeDelivered_Safety * @return TRUE if reset successful, FALSE if not *************************************************************************/ BOOL testResetBloodPrimeSafetyVolumeOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; bloodPrimeVolumeDelivered_Safety.override = OVERRIDE_RESET; bloodPrimeVolumeDelivered_Safety.ovData = bloodPrimeVolumeDelivered_Safety.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetBloodPrimePublishIntervalOverride function sets the override of the * blood prime data publication interval. * @details Inputs: none * @details Outputs: bloodPrimePublishInterval * @param ms milliseconds between blood prime broadcasts * @return TRUE if override set successful, FALSE if not *************************************************************************/ BOOL testSetBloodPrimePublishIntervalOverride( U32 ms ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { U32 intvl = ms / TASK_GENERAL_INTERVAL; result = TRUE; bloodPrimePublishInterval.ovData = intvl; bloodPrimePublishInterval.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetBloodPrimePublishIntervalOverride function resets the override of the * blood prime data publication interval. * @details Inputs: none * @details Outputs: bloodPrimePublishInterval * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetBloodPrimePublishIntervalOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; bloodPrimePublishInterval.override = OVERRIDE_RESET; bloodPrimePublishInterval.ovData = bloodPrimePublishInterval.ovInitData; } return result; } /**@}*/