/************************************************************************** * * 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 FluidBolus.c * * @author (last) Praneeth Bunne * @date (last) 04-Jun-2026 * * @author (original) Praneeth Bunne * @date (original) 04-Jun-2026 * ***************************************************************************/ #include "AlarmMgmtTD.h" #include "BloodFlow.h" #include "DDInterface.h" #include "FluidBolus.h" #include "Messaging.h" #include "Pressures.h" #include "ModeTreatment.h" #include "StateTxBloodPrime.h" #include "StateTxDialysis.h" #include "StateTxPaused.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 BOOL pubBolusPermitted; ///< Flag indicates a bolus is permitted or not to UI (used to broadcast). static BOOL newAlarmIndicateStop; ///< Flag indicating alarm with stop property is triggered. static U32 targetBloodFlowMLPM; ///< Blood pump flow rate (mL/min) to use for current bolus delivery. 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. // ********** 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 handleFluidBolusInProgressState( 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, targetBloodFlowMLPM, totalFluidVolumeDelivered_mL, * bolusFluidVolumeDelivered_mL, fluidBolusStartRequested, * fluidBolusAbortRequested, bolusVolumeLastUpdateTimeStamp, pubBolusPermitted, * newAlarmIndicateStop * @return none *************************************************************************/ void initFluidBolus( void ) { currentFluidBolusState = FLUID_BOLUS_IDLE_STATE; currentFluidBolusMedium = getFluidBolusMedium(); fluidBolusBroadCastTimerCtr = FLUID_BOLUS_DATA_PUB_INTERVAL - 10; // setup to stagger publish from other broadcasters targetBloodFlowMLPM = 0; totalFluidVolumeDelivered_mL = 0.0F; bolusFluidVolumeDelivered_mL = 0.0F; fluidBolusStartRequested = FALSE; fluidBolusAbortRequested = FALSE; bolusVolumeLastUpdateTimeStamp = getMSTimerCount(); pubBolusPermitted = TRUE; newAlarmIndicateStop = FALSE; } /*********************************************************************//** * @brief * The execFluidBolus function executes the fluid bolus state machine. * @details \b Inputs: currentFluidBolusState * @details \b Outputs: currentFluidBolusState * @return none *************************************************************************/ void execFluidBolus( void ) { FLUID_BOLUS_STATE_T priorState = currentFluidBolusState; // If alarm fires while bolus is active, abort the bolus if ( ( TRUE == newAlarmIndicateStop ) && ( TRUE == isFluidBolusActive() ) ) { newAlarmIndicateStop = FALSE; signalAbortFluidBolus(); } switch ( currentFluidBolusState ) { case FLUID_BOLUS_IDLE_STATE: currentFluidBolusState = handleFluidBolusIdleState(); break; case FLUID_BOLUS_WAIT_FOR_PUMPS_STOP_STATE: currentFluidBolusState = handleFluidBolusWait4Pumps2Stop(); break; case FLUID_BOLUS_IN_PROGRESS_STATE: currentFluidBolusState = handleFluidBolusInProgressState(); 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 ); } // Publish fluid bolus data at set interval (whether we are delivering one or not) publishFluidBolusData(); } /*********************************************************************//** * @brief * The getCurrentFluidBolusState function returns the current state of the * Fluid Bolus service. * @details \b Inputs: currentFluidBolusState * @details \b Outputs: none * @return currentFluidBolusState *************************************************************************/ FLUID_BOLUS_STATE_T getCurrentFluidBolusState( void ) { 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 ); //TODO: Uncomment and remove the modality when tube set type logic is implemented //if ( ( TUBE_SET_TYPE_HDF == getTubeSetType() ) if ( ( modality == TREATMENT_MODALITY_HDF ) ) { medium = FLUID_BOLUS_MEDIUM_SUBSTITUTE; } return medium; } /*********************************************************************//** * @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 signalStartFluidBolus function is called by a parent treatment state * to initiate a fluid bolus. * @details \b Inputs: currentFluidBolusState * @details \b Outputs: fluidBolusStartRequested, targetBloodFlowMLPM * @return none *************************************************************************/ void signalStartFluidBolus( U32 flowRate ) { if ( ( FLUID_BOLUS_IDLE_STATE == currentFluidBolusState ) && ( FALSE == fluidBolusStartRequested ) ) { fluidBolusStartRequested = TRUE; targetBloodFlowMLPM = flowRate; } } /*********************************************************************//** * @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 setBolusPermitted function sets the fluid bolus permitted flag, * which is broadcast to the UI to enable or disable the fluid bolus * button on screen. * @details \b Inputs: none * @details \b Outputs: pubBolusPermitted * @param permitted TRUE if a fluid bolus is currently permitted, FALSE otherwise. * @return none *************************************************************************/ void setBolusPermitted( BOOL permitted ) { pubBolusPermitted = permitted; } /*********************************************************************//** * @brief * The publishFluidBolusData function publishes the fluid bolus data * at the set time interval. * @details \b Inputs: fluidBolusBroadCastTimerCtr, currentFluidBolusState, * bolusFluidVolumeDelivered_mL, totalFluidVolumeDelivered_mL, pubBolusPermitted * @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.bolusPermitted = pubBolusPermitted; 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; // Clear when alarm is triggered and can be set when new alarm is triggered. newAlarmIndicateStop = FALSE; // Stop blood pump signalBloodPumpHardStop(); // 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(); // Set arterial & venous valves setValvePosition( H1_VALV, VALVE_POSITION_C_CLOSE ); setValvePosition( H19_VALV, VALVE_POSITION_B_OPEN ); if ( FLUID_BOLUS_MEDIUM_SALINE == currentFluidBolusMedium ) { setBloodPumpTargetFlowRate( targetBloodFlowMLPM, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); } else { // set D92 flow rate cmdSubstitutionRate( (F32)targetBloodFlowMLPM ); } state = FLUID_BOLUS_IN_PROGRESS_STATE; } return state; } /*********************************************************************//** * @brief * The handleFluidBolusInProgressState function handles the in-progress * state of the fluid bolus state machine. Integrates delivered volume from * measured flow rate every tick. For saline medium, fires an alarm if the * saline bag is empty. For substitution medium, aborts if dialysate is no * longer good to deliver. Completes the bolus when the target volume is * reached or an abort is requested. * @details \b Inputs: bolusFluidVolumeDelivered_mL, fluidBolusAbortRequested, * currentFluidBolusMedium * @details \b Outputs: fluidBolusAbortRequested * @return next fluid bolus state *************************************************************************/ static FLUID_BOLUS_STATE_T handleFluidBolusInProgressState( void ) { FLUID_BOLUS_STATE_T state = FLUID_BOLUS_IN_PROGRESS_STATE; F32 bolusTargetVolume = (F32)getTreatmentParameterU32( TREATMENT_PARAM_FLUID_BOLUS_VOLUME ); BOOL isDialysateGoodToDeliver = TRUE; // TODO: replace TRUE with getDialysateGoodToDeliverStatus() when we are ready updateFluidBolusVolumeDelivered(); if ( FLUID_BOLUS_MEDIUM_SALINE == currentFluidBolusMedium ) { // 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; } } else { if ( FALSE == isDialysateGoodToDeliver ) { state = FLUID_BOLUS_IDLE_STATE; } } // Bolus ended or target volume reached or aborted, stop delivery and record volume. if ( ( bolusFluidVolumeDelivered_mL >= bolusTargetVolume ) || ( TRUE == fluidBolusAbortRequested ) || ( FLUID_BOLUS_IDLE_STATE == state ) ) { fluidBolusAbortRequested = FALSE; signalBloodPumpHardStop(); 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: bolusVolumeLastUpdateTimeStamp, targetBloodFlowMLPM * @details \b Outputs: bolusFluidVolumeDelivered_mL, bolusVolumeLastUpdateTimeStamp * @return none *************************************************************************/ static void updateFluidBolusVolumeDelivered( void ) { F32 timeSinceLastVolumeUpdateMin = (F32)calcTimeSince( bolusVolumeLastUpdateTimeStamp ) / (F32)( MS_PER_SECOND * SEC_PER_MIN ); bolusFluidVolumeDelivered_mL += (F32)targetBloodFlowMLPM * 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 handleFluidBolusRequest function handles the UI fluid bolus request. * @details \b Message \b Sent: MSG_ID_TD_FLUID_BOLUS_RESPONSE * @details \b Inputs: none * @details \b Outputs: none * @return TRUE if request is accepted, FALSE if rejected. *************************************************************************/ BOOL handleFluidBolusRequest( MESSAGE_T *message ) { BOOL result = FALSE; U32 cmd = 0; REQUEST_REJECT_REASON_CODE_T rejReason = REQUEST_REJECT_REASON_NONE; FLUID_BOLUS_RESPONSE_PAYLOAD_T response; if ( sizeof(U32) == message->hdr.payloadLen ) { memcpy( &cmd, message->payload, sizeof(U32) ); if ( FLUID_BOLUS_CMD_START == cmd ) { if ( TRUE == isFluidBolusActive() ) { // TD Fluid Bolus Additional Bolus Prevention rejReason = REQUEST_REJECT_REASON_FLUID_BOLUS_IN_PROGRESS; } else { // Route to whichever calling state is currently active switch ( getTreatmentState() ) { case TREATMENT_BLOOD_PRIME_STATE: result = signalBloodPrimeFluidBolusRequest(); rejReason = ( result == TRUE ) ? REQUEST_REJECT_REASON_NONE : REQUEST_REJECT_REASON_INVALID_TREATMENT_SUB_STATE; break; case TREATMENT_DIALYSIS_STATE: result = signalDialysisFluidBolusRequest(); rejReason = ( result == TRUE ) ? REQUEST_REJECT_REASON_NONE : REQUEST_REJECT_REASON_INVALID_TREATMENT_SUB_STATE; break; case TREATMENT_PAUSED_STATE: result = signalPauseTreatFluidBolusRequest(); rejReason = ( result == TRUE ) ? REQUEST_REJECT_REASON_NONE : REQUEST_REJECT_REASON_INVALID_TREATMENT_SUB_STATE; break; // case TREATMENT_HDF_STATE: // result = signalHdfFluidBolusRequest(); // rejReason = ( result == TRUE ) ? REQUEST_REJECT_REASON_NONE : REQUEST_REJECT_REASON_INVALID_TREATMENT_SUB_STATE; // break; // // case TREATMENT_ISO_UF_STATE: // result = signalIsoUFFluidBolusRequest(); // rejReason = ( result == TRUE ) ? REQUEST_REJECT_REASON_NONE : REQUEST_REJECT_REASON_INVALID_TREATMENT_SUB_STATE; // break; // // case TREATMENT_END_STATE: // result = signalTreatmentEndFluidBolusRequest(); // rejReason = ( result == TRUE ) ? REQUEST_REJECT_REASON_NONE : REQUEST_REJECT_REASON_INVALID_TREATMENT_SUB_STATE; // break; default: rejReason = REQUEST_REJECT_REASON_INVALID_TREATMENT_STATE; break; } } } else if ( FLUID_BOLUS_CMD_STOP == cmd ) { if ( TRUE == isFluidBolusActive() ) { signalAbortFluidBolus(); result = TRUE; rejReason = REQUEST_REJECT_REASON_NONE; } else { rejReason = REQUEST_REJECT_REASON_FLUID_BOLUS_NOT_IN_PROGRESS; } } else { rejReason = REQUEST_REJECT_REASON_INVALID_COMMAND; } } else { rejReason = REQUEST_REJECT_REASON_INVALID_REQUEST_FORMAT; } response.accepted = result; response.rejectionReason = rejReason; response.targetVolumeMl = getTreatmentParameterU32( TREATMENT_PARAM_FLUID_BOLUS_VOLUME ); sendMessage( MSG_ID_TD_FLUID_BOLUS_RESPONSE, COMM_BUFFER_OUT_CAN_TD_2_UI, (U08*)&response, sizeof( FLUID_BOLUS_RESPONSE_PAYLOAD_T ) ); return result; } /*********************************************************************//** * @brief * The signalNewStopAlarmActivated function is called by the alarm system * when an alarm with the stop property is activated. * @details \b Inputs: none * @details \b Outputs: newAlarmIndicateStop * @return none *************************************************************************/ void signalNewStopAlarmActivated( void ) { newAlarmIndicateStop = TRUE; } /**@}*/