/************************************************************************** * * Copyright (c) 2026-2026 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 StateTxBloodPrime.c * * @author (last) Varshini Nagabooshanam * @date (last) 29-Jan-2026 * * @author (original) Varshini Nagabooshanam * @date (original) 09-Jan-2026 * ***************************************************************************/ #include "AirTrap.h" #include "BloodFlow.h" #include "Buttons.h" #include "ModeTreatment.h" #include "TxParams.h" #include "OperationModes.h" #include "StateTxBloodPrime.h" #include "Switches.h" #include "TaskGeneral.h" #include "Utilities.h" #include "Valves.h" #include "Valve3Way.h" /** * @addtogroup StateTxBloodPrime * @{ */ // ********** private definitiions *********** ///< Minimum ramp time for blood prime (in seconds) #define MIN_RAMP_TIME_SEC 60 /// Initial flow rate for blood pump when starting blood prime operation. #define BLOOD_PRIME_INIT_BP_FLOW_RATE_ML_MIN 250 /// 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.0F / (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 REQUESTED_BLOOD_PRIME_USER_ACTIONS_T bloodPrimeRequestedAction = NUM_OF_REQUESTED_BLOOD_PRIME_USER_ACTIONS; ///< Number of requested blood prime user actions static F32 bloodPrimeRampFlowRate_mL_min; ///< Current blood pump ramp flow rate. static F32 bloodPrimeRampStep_mL; ///< Blood pump volume step size for ramping. static F32 bloodPrimeTargetVolume_mL; ///< Calculated target blood prime volume (based on selected dialyzer and fixed tubing volume). static F32 lastBloodPrimeFlowRate_mL_min; ///< Timer counter for determining for last blood prime status. 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) from measured blood flow rate. // ********** private function prototypes ********** static void resetBloodPrimeFlags( void ); static F32 getBloodPrimeVolume( void ); static BLOOD_PRIME_STATE_T handleBloodPrimeRampState( void ); static BLOOD_PRIME_STATE_T handleBloodPrimeRunState( void ); static BLOOD_PRIME_STATE_T handleBloodPrimePausedState( void ); static void publishBloodPrimeData( void ); /*********************************************************************//** * @brief * The initBloodPrime function initializes the Blood Prime state unit. * @details \b Inputs: none * @details \b Outputs: Blood prime state unit initialized. * @return none *************************************************************************/ void initBloodPrime( void ) { S32 setBPRate = (S32)getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ); F32 rampRateSpan = 0.0F; F32 estRampSeconds = 0.0F; bloodPrimeState = BLOOD_PRIME_RAMP_STATE; bloodPrimeRampControlTimerCtr = 0; bloodPrimePublishTimerCtr = BLOOD_PRIME_DATA_PUBLISH_INTERVAL - 2; // setup so publish will occur time after next cumulativeBloodPrimeVolume_mL.data = 0.0; resetBloodPrimeFlags(); bloodPrimeTargetVolume_mL = TUBING_BLOOD_PRIME_VOLUME_ML + (F32)( getDialyzerBloodVolume( getTreatmentParameterU32( TREATMENT_PARAM_DIALYZER_TYPE ) ) ); bloodPrimeRampFlowRate_mL_min = (F32)BLOOD_PRIME_INIT_BP_FLOW_RATE_ML_MIN; bloodPrimeRequestedAction = NUM_OF_REQUESTED_BLOOD_PRIME_USER_ACTIONS; // Calculate BP ramp step size rampRateSpan = (F32)( setBPRate - BLOOD_PRIME_INIT_BP_FLOW_RATE_ML_MIN ); estRampSeconds = ( ( bloodPrimeTargetVolume_mL / (F32)setBPRate + bloodPrimeTargetVolume_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 * state. This function will reset only what is required for resuming a * blood prime that was paused (alarm) so if a blood prime is being restarted, * call the initialize function first to reset everything. * @details \b Inputs: none * @details \b Outputs: actuators set for initial state of blood prime state * @return none *************************************************************************/ void transitionToBloodPrime( void ) { // Set user alarm recovery actions allowed in this state setAlarmUserActionEnabled( ALARM_USER_ACTION_RESUME, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_RINSEBACK, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_END_TREATMENT, TRUE ); // Require BP door to be closed during this state doorClosedRequired( TRUE ); // Set pinch valves setValvePosition( H1_VALV, VALVE_POSITION_B_OPEN ); // set arterial valve to pump blood from patient setValvePosition( H19_VALV, VALVE_POSITION_B_OPEN ); // set venous valve to open, allowing blood to return to patient // Set air trap valves set3WayValveState( H13_VALV, VALVE_3WAY_COMMON_TO_CLOSED_STATE ); set3WayValveState( H20_VALV, VALVE_3WAY_COMMON_TO_CLOSED_STATE ); // Start air trap control startAirTrapControl(); // Ensure Heparin pump is stopped //stopSyringePump(); // start blood pump setBloodPumpTargetFlowRate( (U32)bloodPrimeRampFlowRate_mL_min, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); // TODO - direct DD to bypass dialysate and perform no ultrafiltration setCurrentSubState( (U32)bloodPrimeState ); } /*********************************************************************//** * @brief * The resetBloodPrimeFlags function resets the blood prime request flags. * @details \b Inputs: none * @details \b 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 \b Inputs: cumulativeBloodPrimeVolume_mL * @details \b 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 execBloodPrime function executes the Blood Prime state machine. * @details \b Inputs: bloodPrimeState * @details \b Outputs: bloodPrimeState * @return none *************************************************************************/ void execBloodPrime( void ) { switch ( bloodPrimeState ) { case BLOOD_PRIME_RAMP_STATE: bloodPrimeState = handleBloodPrimeRampState(); break; case BLOOD_PRIME_RUN_STATE: bloodPrimeState = handleBloodPrimeRunState(); break; case BLOOD_PRIME_PAUSED_STATE: bloodPrimeState = handleBloodPrimePausedState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_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 \b Inputs: flags * @details \b 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 ); if ( TRUE == getTestConfigStatus( TEST_CONFIG_SKIP_BLOOD_PRIME ) ) { cumulativeBloodPrimeVolume_mL.data = bloodPrimeTargetVolume_mL; } // Volume reached to Dialysis if ( getBloodPrimeVolume() >= bloodPrimeTargetVolume_mL ) { initBloodPrime(); setBloodIsPrimed( TRUE ); signalBloodPrimeToDialysis(); } // Soft pause from UI else if ( bloodPrimeRequestedAction == REQUESTED_USER_ACTION_BLOOD_PRIME_PAUSE ) { bloodPrimeRequestedAction = NUM_OF_REQUESTED_BLOOD_PRIME_USER_ACTIONS; lastBloodPrimeFlowRate_mL_min = getMeasuredBloodFlowRate(); setBloodPumpTargetFlowRate( 0, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); result = BLOOD_PRIME_PAUSED_STATE; } else if ( getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ) != lastBloodPrimeFlowRate_mL_min ) { result = BLOOD_PRIME_RUN_STATE; } else { // Continue ramping if ( ++bloodPrimeRampControlTimerCtr >= BLOOD_PRIME_RAMPING_INTERVAL ) { U32 setBPRate = getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ); setBloodPumpTargetFlowRate( setBPRate, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); bloodPrimeRampControlTimerCtr = 0; } } return result; } /*********************************************************************//** * @brief * The handleBloodPrimeRunState function handles the blood prime run * state operations. * @details \b Inputs: cumulativeBloodPrimevolume_mL * @details \b Outputs: cumulativeBloodPrimevolume_mL * @return next blood prime state *************************************************************************/ static BLOOD_PRIME_STATE_T handleBloodPrimeRunState( void ) { BLOOD_PRIME_STATE_T result = BLOOD_PRIME_RUN_STATE; // Update blood prime volume delivered so far cumulativeBloodPrimeVolume_mL.data += ( getMeasuredBloodFlowRate() * BLOOD_PRIME_FLOW_INTEGRATOR ); if ( getBloodPrimeVolume() >= bloodPrimeTargetVolume_mL ) { initBloodPrime(); setBloodIsPrimed( TRUE ); signalBloodPrimeToDialysis(); } // Soft pause else if ( bloodPrimeRequestedAction == REQUESTED_USER_ACTION_BLOOD_PRIME_PAUSE ) { bloodPrimeRequestedAction = NUM_OF_REQUESTED_BLOOD_PRIME_USER_ACTIONS; lastBloodPrimeFlowRate_mL_min = getMeasuredBloodFlowRate(); setBloodPumpTargetFlowRate( 0, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); result = BLOOD_PRIME_PAUSED_STATE; } else { // Run continuously at requested rate setBloodPumpTargetFlowRate( getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ), MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); } return result; } /*********************************************************************//** * @brief * The handleBloodPrimePausedState function handles the blood prime paused * state operations. * @details \b Inputs: getCurrentOperationMode * @details \b Outputs: flags handled * @return none *************************************************************************/ static BLOOD_PRIME_STATE_T handleBloodPrimePausedState( void ) { BLOOD_PRIME_STATE_T result = BLOOD_PRIME_PAUSED_STATE; // Hard stop to exit blood prime if ( TRUE == isStopButtonPressed() ) { initBloodPrime(); setBloodPumpTargetFlowRate( 0, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); result = BLOOD_PRIME_RAMP_STATE; } // Resume from UI else if ( bloodPrimeRequestedAction == REQUESTED_USER_ACTION_BLOOD_PRIME_RESUME ) { bloodPrimeRequestedAction = NUM_OF_REQUESTED_BLOOD_PRIME_USER_ACTIONS; setValvePosition( H1_VALV, VALVE_POSITION_B_OPEN ); setValvePosition( H19_VALV, VALVE_POSITION_B_OPEN ); setBloodPumpTargetFlowRate( lastBloodPrimeFlowRate_mL_min, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); if ( getMeasuredBloodFlowRate() < getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ) ) { result = BLOOD_PRIME_RAMP_STATE; } else { result = BLOOD_PRIME_RUN_STATE; } } return result; } /*********************************************************************//** * @brief * The getCurrentBloodPrimeState function returns the current state of the * blood prime state. * @details \b Inputs: bloodPrimeState * @details \b Outputs: none * @return bloodPrimeState *************************************************************************/ BLOOD_PRIME_STATE_T getCurrentBloodPrimeState( void ) { return bloodPrimeState; } /*********************************************************************//** * @brief * The bloodPrimeHandleCmdRequest function handles the UI blood prime * command request. * @details \b Message \b Sent: MSG_ID_UI_BLOOD_PRIME_CMD_REQUEST * @details \b Inputs: message containing requested blood prime user action. * @details \b Outputs: bloodPrimeRequestedAction updated if valid. * @return TRUE if payload is valid, FALSE otherwise. *************************************************************************/ BOOL bloodPrimeHandleCmdRequest( MESSAGE_T *message ) { REQUESTED_BLOOD_PRIME_USER_ACTIONS_T requestedAction; if ( sizeof(requestedAction) == message->hdr.payloadLen ) { memcpy( &requestedAction, message->payload[0], sizeof( requestedAction ) ); if ( requestedAction < NUM_OF_REQUESTED_BLOOD_PRIME_USER_ACTIONS ) { bloodPrimeRequestedAction = requestedAction; } return TRUE; } return FALSE; } /*********************************************************************//** * @brief * The publishBloodPrimeData function publishes blood prime data * at interval. * @details \b Message \b Sent: MSG_ID_TD_BLOOD_PRIME_PROGRESS_DATA * @details \b Inputs: bloodPrimePublishTimerCtr, * bloodPrimePublishInterval * @details \b Outputs: blood prime data are published to CAN bus * @return none *************************************************************************/ static void publishBloodPrimeData( void ) { if ( ++bloodPrimePublishTimerCtr >= getU32OverrideValue( &bloodPrimePublishInterval ) ) { BLOOD_PRIME_DATA_PAYLOAD_T data; bloodPrimePublishTimerCtr = 0; data.targetBloodPrimeVolumeMl = bloodPrimeTargetVolume_mL; data.deliveredBloodPrimeVolumeMl = MAX( 0.0, getBloodPrimeVolume() ); broadcastData( MSG_ID_TD_BLOOD_PRIME_PROGRESS_DATA, COMM_BUFFER_OUT_CAN_TD_BROADCAST, (U08*)&data, sizeof( BLOOD_PRIME_DATA_PAYLOAD_T ) ); } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testBloodPrimePublishIntervalOverride function overrides the interval * at which the TD blood prime data is published. * @details \b Inputs: none * @details \b Outputs: bloodPrimePublishInterval * @param message Override message from Dialin which includes the interval * to override the blood prime broadcast interval to. * @return TRUE if override request is successful, FALSE if not *************************************************************************/ BOOL testBloodPrimePublishIntervalOverride( MESSAGE_T *message ) { BOOL result = u32BroadcastIntervalOverride( message, &bloodPrimePublishInterval, TASK_GENERAL_INTERVAL ); return result; } /*********************************************************************//** * @brief * The testBloodPrimeVolumeOverride function override the blood prime volume. * @details \b Inputs: none * @details \b Outputs: cumulativeBloodPrimeVolume_mL * @param message Override message from Dialin which includes the blood * prime volume to override it to. * @return TRUE if override request is successful, FALSE if not *************************************************************************/ BOOL testBloodPrimeVolumeOverride( MESSAGE_T *message ) { BOOL result = f32Override( message, &cumulativeBloodPrimeVolume_mL ); return result; } /**@}*/