/************************************************************************** * * Copyright (c) 2025-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 FluidBolus.c * * @author (last) Praneeth Bunne * @date (last) 01-May-2026 * * @author (original) Praneeth Bunne * @date (original) 01-May-2026 * ***************************************************************************/ #include "AlarmMgmtTD.h" #include "BloodFlow.h" #include "DDInterface.h" #include "FluidBolus.h" #include "Messaging.h" #include "Pressures.h" #include "TaskGeneral.h" #include "Timers.h" #include "TxParams.h" #include "Valves.h" /** * @addtogroup FluidBolus * @{ */ // ********** private definitions ********** static const U32 FLUID_BOLUS_DATA_PUB_INTERVAL = ( MS_PER_SECOND / TASK_GENERAL_INTERVAL); ///< Saline bolus data broadcast interval (ms/task time) count. // ********** private data ********** static FLUID_BOLUS_STATE_T currentFluidBolusState; ///< Current fluid bolus state static FLUID_BOLUS_MEDIUM_T currentFluidBolusMedium; ///< Current fluid bolus medium static U32 fluidBolusBroadCastTimerCtr; ///< Broadcast Timer counter static BOOL fluidBolusStartRequested; ///< Flag indicates a fluid bolus start has been requested by user. static BOOL fluidBolusAbortRequested; ///< Flag indicates a fluid bolus abort has been requested by user. static F32 totalFluidVolumeDelivered_mL; ///< Volume (mL) in total of fluid delivered so far (cumulative for all boluses including current one). static F32 bolusFluidVolumeDelivered_mL; ///< Volume (mL) of current bolus delivered so far (calculated from measured blood flow rate). static U32 bolusVolumeLastUpdateTimeStamp; ///< Time stamp for last bolus volume update. ///< Permitted alarm list-bolus allowed from paused state static const ALARM_ID_T FLUID_BOLUS_PERMITTED_PAUSED_ALARMS[] = { ALARM_ID_TD_TREATMENT_STOPPED_BY_USER, ALARM_ID_TD_ARTERIAL_PRESSURE_HIGH, ALARM_ID_TD_ARTERIAL_PRESSURE_LOW, ALARM_ID_TD_VENOUS_PRESSURE_HIGH, ALARM_ID_TD_TMP_PRESSURE_HIGH, ALARM_ID_TD_TMP_PRESSURE_LOW, }; // ********** private function prototypes ********** static void updateFluidBolusVolumeDelivered( void ); static void completeBolusToCumulative( void ); static FLUID_BOLUS_STATE_T handleFluidBolusIdleState( void ); static FLUID_BOLUS_STATE_T handleFluidBolusWait4Pumps2Stop( void ); static FLUID_BOLUS_STATE_T handleFluidBolusSalineInProgressState( void ); static FLUID_BOLUS_STATE_T handleFluidBolusSubstituteInProgressState( void ); /*********************************************************************//** * @brief * The initFluidBolus function initializes the Fluid Bolus module. * Calling this function resets all bolus state and cumulative volume, * and therefore should only be called when a new treatment is due to begin. * @details \b Inputs: none * @details \b Outputs: currentFluidBolusState, currentFluidBolusMedium, * fluidBolusBroadCastTimerCtr, totalFluidVolumeDelivered_mL, * bolusFluidVolumeDelivered_mL, fluidBolusStartRequested, * fluidBolusAbortRequested, bolusVolumeLastUpdateTimeStamp * @return none *************************************************************************/ void initFluidBolus( void ) { currentFluidBolusState = FLUID_BOLUS_IDLE_STATE; currentFluidBolusMedium = getFluidBolusMedium(); fluidBolusBroadCastTimerCtr = 0; totalFluidVolumeDelivered_mL = 0.0F; bolusFluidVolumeDelivered_mL = 0.0F; fluidBolusStartRequested = FALSE; fluidBolusAbortRequested = FALSE; bolusVolumeLastUpdateTimeStamp = getMSTimerCount(); } /*********************************************************************//** * @brief * The execFluidBolus function executes the fluid bolus state machine. * @details \b Inputs: currentFluidBolusState * @details \b Outputs: currentFluidBolusState * @return Current fluid bolus state machine. *************************************************************************/ FLUID_BOLUS_STATE_T execFluidBolus( void ) { FLUID_BOLUS_STATE_T priorState = currentFluidBolusState; switch ( currentFluidBolusState ) { case FLUID_BOLUS_IDLE_STATE: currentFluidBolusState = handleFluidBolusIdleState(); break; case FLUID_BOLUS_WAIT_FOR_PUMPS_STOP_STATE: currentFluidBolusState = handleFluidBolusWait4Pumps2Stop(); break; case FLUID_BOLUS_SALINE_IN_PROGRESS_STATE: currentFluidBolusState = handleFluidBolusSalineInProgressState(); break; case FLUID_BOLUS_SUBSITUTE_IN_PROGRESS_STATE: currentFluidBolusState = handleFluidBolusSubstituteInProgressState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_FLUID_BOLUS_STATE, currentFluidBolusState ) currentFluidBolusState = FLUID_BOLUS_IDLE_STATE; break; } if ( priorState != currentFluidBolusState ) { SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_SUB_STATE_CHANGE, priorState, currentFluidBolusState ); } return currentFluidBolusState; } /*********************************************************************//** * @brief * The getFluidBolusMedium function determines the fluid bolus medium based * on the current treatment modality and online fluid configuration. * Substitution fluid applies to HDF treatment, or HD treatment with online * fluid enabled. Saline applies to standard HD without online fluid. * @details \b Inputs: none * @details \b Outputs: none * @return FLUID_BOLUS_MEDIUM_SUBSTITUTE for HDF or HD-online, FLUID_BOLUS_MEDIUM_SALINE otherwise. *************************************************************************/ FLUID_BOLUS_MEDIUM_T getFluidBolusMedium( void ) { FLUID_BOLUS_MEDIUM_T medium = FLUID_BOLUS_MEDIUM_SALINE; U32 modality = getTreatmentParameterU32( TREATMENT_PARAM_TREATMENT_MODALITY ); if ( ( modality == TREATMENT_MODALITY_HDF ) ) // || ( modality == TREATMENT_MODALITY_HD ) && ( check online fluid enabled ) { medium = FLUID_BOLUS_MEDIUM_SUBSTITUTE; } return medium; } /*********************************************************************//** * @brief * The getFluidBolusState function gets the current fluid bolus state. * @details \b Inputs: currentFluidBolusState * @details \b Outputs: none * @return currentFluidBolusState *************************************************************************/ FLUID_BOLUS_STATE_T getFluidBolusState( void ) { return currentFluidBolusState; } /*********************************************************************//** * @brief * The isFluidBolusActive function determines whether a fluid bolus is * currently being delivered. * @details \b Inputs: currentFluidBolusState * @details \b Outputs: none * @return TRUE if bolus state is not IDLE, FALSE otherwise. *************************************************************************/ BOOL isFluidBolusActive( void ) { return ( FLUID_BOLUS_IDLE_STATE != currentFluidBolusState ) ? TRUE : FALSE; } /*********************************************************************//** * @brief * The getTotalFluidBolusVolumeDelivered function gets the current total * fluid volume delivered. * @details \b Inputs: totalFluidVolumeDelivered * @details \b Outputs: none * @return totalFluidVolumeDelivered *************************************************************************/ F32 getTotalFluidBolusVolumeDelivered( void ) { return totalFluidVolumeDelivered_mL; } /*********************************************************************//** * @brief * The getCurrentFluidBolusVolumeDelivered function gets the volume * delivered for the currently active bolus. * @details \b Inputs: bolusFluidVolumeDelivered_mL * @details \b Outputs: none * @return current bolus volume delivered in mL. *************************************************************************/ F32 getCurrentFluidBolusVolumeDelivered( void ) { return bolusFluidVolumeDelivered_mL; } /*********************************************************************//** * @brief * The signalStartFluidBolus function is called by a parent treatment state * to initiate a fluid bolus. * @details \b Inputs: currentFluidBolusState * @details \b Outputs: fluidBolusStartRequested * @return TRUE if state is IDLE, FALSE otherwise. *************************************************************************/ BOOL signalStartFluidBolus( void ) { BOOL result = FALSE; if ( ( FLUID_BOLUS_IDLE_STATE == currentFluidBolusState ) && ( FALSE == fluidBolusStartRequested ) ) { fluidBolusStartRequested = TRUE; result = TRUE; } return result; } /*********************************************************************//** * @brief * The signalAbortFluidBolus function signals an external abort to the * fluid bolus service. Called by user-stop or the global alarm-stop hook. * @details \b Inputs: currentFluidBolusState * @details \b Outputs: fluidBolusAbortRequested * @return none *************************************************************************/ void signalAbortFluidBolus( void ) { if ( FLUID_BOLUS_IDLE_STATE != currentFluidBolusState ) { fluidBolusAbortRequested = TRUE; } } /*********************************************************************//** * @brief * The publishFluidBolusData function publishes the fluid bolus data * at the set time interval. * @details \b Inputs: fluidBolusBroadCastTimerCtr, currentFluidBolusState, * bolusFluidVolumeDelivered_mL, totalFluidVolumeDelivered_mL * @details \b Outputs: fluidBolusBroadCastTimerCtr * @return none *************************************************************************/ void publishFluidBolusData( void ) { if ( ++fluidBolusBroadCastTimerCtr >= FLUID_BOLUS_DATA_PUB_INTERVAL ) { FLUID_BOLUS_DATA_PAYLOAD_T data; data.tgtFluidVolumeMl = getTreatmentParameterU32( TREATMENT_PARAM_FLUID_BOLUS_VOLUME ); data.bolFluidVolumeMl = bolusFluidVolumeDelivered_mL; data.cumFluidVolumeMl = totalFluidVolumeDelivered_mL; data.fluidBolusState = currentFluidBolusState; broadcastData( MSG_ID_TD_FLUID_BOLUS_DATA, COMM_BUFFER_OUT_CAN_TD_BROADCAST, (U08*)&data, sizeof( FLUID_BOLUS_DATA_PAYLOAD_T ) ); fluidBolusBroadCastTimerCtr = 0; } } /*********************************************************************//** * @brief * The handleFluidBolusIdleState function handles the idle state of the * fluid bolus state machine. * @details \b Inputs: fluidBolusStartRequested * @details \b Outputs: fluidBolusStartRequested, bolusVolumeLastUpdateTimeStamp * @return next fluid bolus state *************************************************************************/ static FLUID_BOLUS_STATE_T handleFluidBolusIdleState( void ) { FLUID_BOLUS_STATE_T state = FLUID_BOLUS_IDLE_STATE; // Handle fluid bolus start request if ( TRUE == fluidBolusStartRequested ) { fluidBolusStartRequested = FALSE; // Stop blood pump setBloodPumpTargetFlowRate( 0, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); // Stop substitution pump cmdSubstitutionRate( 0.0F ); bolusVolumeLastUpdateTimeStamp = getMSTimerCount(); state = FLUID_BOLUS_WAIT_FOR_PUMPS_STOP_STATE; } return state; } /*********************************************************************//** * @brief * The handleFluidBolusWait4Pumps2Stop function handles the wait for pumps * to stop state of the fluid bolus state machine. * @details \b Inputs: fluidBolusAbortRequested, currentFluidBolusMedium * @details \b Outputs: fluidBolusAbortRequested, bolusFluidVolumeDelivered_mL, * bolusVolumeLastUpdateTimeStamp * @return next fluid bolus state *************************************************************************/ static FLUID_BOLUS_STATE_T handleFluidBolusWait4Pumps2Stop( void ) { FLUID_BOLUS_STATE_T state = FLUID_BOLUS_WAIT_FOR_PUMPS_STOP_STATE; if( TRUE == fluidBolusAbortRequested ) { fluidBolusAbortRequested = FALSE; signalBloodPumpHardStop(); state = FLUID_BOLUS_IDLE_STATE; } else if ( FALSE == isBloodPumpRunning() ) // TODO: Write condition to check D92 when HDF is implemented { // Reset bolus data before we start bolusFluidVolumeDelivered_mL = 0.0; bolusVolumeLastUpdateTimeStamp = getMSTimerCount(); setValvePosition( H1_VALV, VALVE_POSITION_C_CLOSE ); setValvePosition( H19_VALV, VALVE_POSITION_B_OPEN ); if ( FLUID_BOLUS_MEDIUM_SALINE == currentFluidBolusMedium ) { setBloodPumpTargetFlowRate( getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ), MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); state = FLUID_BOLUS_SALINE_IN_PROGRESS_STATE; } else if ( TRUE == isDialysateGoodToDeliver() ) { // set D92 flow rate cmdSubstitutionRate( getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ) ); state = FLUID_BOLUS_SUBSITUTE_IN_PROGRESS_STATE; } else { // No actuator action required. state = FLUID_BOLUS_IDLE_STATE; } } return state; } /*********************************************************************//** * @brief * The handleFluidBolusSalineInProgressState function handles the saline * in-progress state of the fluid bolus state machine. Integrates delivered * volume from measured blood flow, fires an alarm if the saline bag is empty, * and completes the bolus when the target volume is reached or an abort is requested. * @details \b Inputs: bolusFluidVolumeDelivered_mL, fluidBolusAbortRequested * @details \b Outputs: fluidBolusAbortRequested, * @return next fluid bolus state *************************************************************************/ static FLUID_BOLUS_STATE_T handleFluidBolusSalineInProgressState( void ) { FLUID_BOLUS_STATE_T state = FLUID_BOLUS_SALINE_IN_PROGRESS_STATE; F32 bolusTargetVolume = (F32)getTreatmentParameterU32( TREATMENT_PARAM_FLUID_BOLUS_VOLUME ); updateFluidBolusVolumeDelivered(); // Check for empty saline bag per arterial line pressure if ( TRUE == isSalineBagEmpty() ) { SET_ALARM_WITH_1_F32_DATA( ALARM_ID_TD_EMPTY_SALINE_BAG, getFilteredArterialPressure() ); state = FLUID_BOLUS_IDLE_STATE; } // Determine if bolus is complete or stopped by user else if ( ( bolusFluidVolumeDelivered_mL >= bolusTargetVolume ) || ( TRUE == fluidBolusAbortRequested ) ) { fluidBolusAbortRequested = FALSE; state = FLUID_BOLUS_IDLE_STATE; } // complete or abort or alarm if ( state != FLUID_BOLUS_SALINE_IN_PROGRESS_STATE ) { // Hard stop blood signalBloodPumpHardStop(); completeBolusToCumulative(); } return state; } /*********************************************************************//** * @brief * The handleFluidBolusSubstituteInProgressState function handles the * substitute in-progress state state of the fluid bolus state machine. * Integrates delivered volume from measured blood flow. Monitors dialysate * readiness every tick, aborts if dialysate is no longer good to deliver. * Completes the bolus when target is reached or an abort is requested. * @details \b Inputs: bolusFluidVolumeDelivered_mL, fluidBolusAbortRequested * @details \b Outputs: fluidBolusAbortRequested, * totalFluidVolumeDelivered_mL via completeBolusToCumulative() * @return next fluid bolus state. *************************************************************************/ static FLUID_BOLUS_STATE_T handleFluidBolusSubstituteInProgressState( void ) { FLUID_BOLUS_STATE_T state = FLUID_BOLUS_SUBSITUTE_IN_PROGRESS_STATE; F32 bolusTargetVolume = (F32)getTreatmentParameterU32( TREATMENT_PARAM_FLUID_BOLUS_VOLUME ); updateFluidBolusVolumeDelivered(); // Check for dialysate, target volume delivered or abort from user if ( ( FALSE == isDialysateGoodToDeliver() ) || ( bolusFluidVolumeDelivered_mL >= bolusTargetVolume ) || ( TRUE == fluidBolusAbortRequested ) ) { fluidBolusAbortRequested = FALSE; // TODO: d92 to restore or turn off if modality is hd. cmdSubstitutionRate( 0.0F ); completeBolusToCumulative(); state = FLUID_BOLUS_IDLE_STATE; } return state; } /*********************************************************************//** * @brief * The updateFluidBolusVolumeDelivered function integrates the volume * delivered for the active bolus. * @details \b Inputs: currentFluidBolusMedium, bolusVolumeLastUpdateTimeStamp * @details \b Outputs: bolusFluidVolumeDelivered_mL, bolusVolumeLastUpdateTimeStamp * @return none *************************************************************************/ static void updateFluidBolusVolumeDelivered( void ) { F32 timeSinceLastVolumeUpdateMin = (F32)calcTimeSince( bolusVolumeLastUpdateTimeStamp ) / (F32)( MS_PER_SECOND * SEC_PER_MIN ); F32 rate_mL_min; if ( FLUID_BOLUS_MEDIUM_SALINE == currentFluidBolusMedium ) { rate_mL_min = getMeasuredBloodFlowRate(); } else { //TODO rate_mL_min = (F32)getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ); } bolusFluidVolumeDelivered_mL += rate_mL_min * timeSinceLastVolumeUpdateMin; bolusVolumeLastUpdateTimeStamp = getMSTimerCount(); } /*********************************************************************//** * @brief * The completeBolusToCumulative function rolls the per-bolus delivered * volume into the cumulative counter and emits a treatment log event. * @details \b Inputs: bolusFluidVolumeDelivered_mL * @details \b Outputs: totalFluidVolumeDelivered_mL, bolusFluidVolumeDelivered_mL, * @return none *************************************************************************/ static void completeBolusToCumulative( void ) { totalFluidVolumeDelivered_mL += bolusFluidVolumeDelivered_mL; SEND_EVENT_WITH_2_F32_DATA( FLUID_BOLUSES_CHANGE_EVENT, bolusFluidVolumeDelivered_mL, totalFluidVolumeDelivered_mL ); bolusFluidVolumeDelivered_mL = 0.0F; } /*********************************************************************//** * @brief * The areAllActiveAlarmsPermittedForBolus function checks whether all * currently active alarms are from permitted list. * @details \b Inputs: FLUID_BOLUS_PERMITTED_PAUSED_ALARMS[] * @details \b Outputs: none * @return TRUE if all active alarms are from the permitted list and at * least one permitted alarm is active. FALSE otherwise. *************************************************************************/ BOOL areAllActiveAlarmsPermittedForBolus( void ) { U32 i; U32 permittedCount = 0U; U32 totalCount = getActiveAlarmCount(); BOOL result = FALSE; if ( 0U == totalCount ) { return FALSE; } for ( i = 0U; i < 6; i++ ) { if ( TRUE == isAlarmActive( FLUID_BOLUS_PERMITTED_PAUSED_ALARMS[ i ] ) ) { permittedCount++; } } result = ( permittedCount == totalCount ) ? TRUE: FALSE; return result; } /**@}*/