/************************************************************************** * * 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 HemoDiaFiltration.c * * @author (last) Steve Jarpe * @date (last) 23-May-2026 * * @author (original) Steve Jarpe * @date (original) 23-May-2026 * ***************************************************************************/ #include "PIControllers.h" #include "Pressures.h" #include "Messaging.h" #include "OperationModes.h" #include "TaskGeneral.h" #include "TestSupport.h" #include "DDInterface.h" #include "BloodFlow.h" #include "HemoDiaFiltration.h" /** * @addtogroup Hemodiafiltration * @{ */ // ********** private definitions ********** #define HDF_DATA_PUBLISH_INTERVAL ( 1000 / TASK_GENERAL_INTERVAL ) ///< Interval (ms/task time) at which the hemodiafiltration data published. #define SECONDS_PER_MINUTE 60 ///< seconds per minute #define HDF_CONTROL_PERIOD_SECONDS 2 ///< Interval at which the hemodiafiltration flow rate adjustment is executed in seconds #define HDF_CONTROL_PERIOD_MS ( HDF_CONTROL_PERIOD_SECONDS * MS_PER_SECOND ) ///< Interval at which the hemodiafiltration flow rate adjustment is executed in milliseconds #define HDF_CONTROL_INTERVAL ( HDF_CONTROL_PERIOD_MS / TASK_GENERAL_INTERVAL ) ///< Interval (ms/task time) at which the peroidic hemodiafiltration flow rate is calculated. #define ZERO_RATE 0.0F ///< Zero value. #define SUB_PUMP_PRESSURE_CONTROL_P_COEFFICIENT 0.5F ///< P term for Sub pump pressure control. #define SUB_PUMP_PRESSURE_CONTROL_I_COEFFICIENT 5.0F ///< I term for Sub pump pressure control. // Parameters that will be set in some other place , set here for testing #define HDF_MAX_FILTRATION_FRACTION 0.35F ///< HDF maximum filtration fraction (Qs+Quf)/Qb #define HDF_MAX_TMP 400.0F ///< HDF maximum TMP - trans-membrane pressure = (Pb - Pd) #define HDF_MAX_RATE 200.0F ///< Maximum substitution flow rate // ********** private data ********** static HDF_EXEC_STATE_T hdfExecState; ///< Current hemodiafiltration executive state. static BOOL isHemodiafiltrationRequested; ///< Flag indicating hemodiafiltration request. static U32 hdfControlTimerCounter; ///< Counter (in task interval) to initiate the periodic hemodiafiltration rate recalculation. static F32 hdfCurrentRate; ///< current HDF rate in ml/min static F32 hdfTMPTarget; ///< HDF target TMP static F32 hdfManualRate; ///< Manually set HDF rate static F32 hdfMaximumVolume; ///< Maximum HDF volume setting static F32 hdfCurrentVolume; ///< current HDF delivered substitution volume static F32 hdfLastActiveRate; ///< Saved pump rate for resuming after pause. static U32 hdfDataPublicationTimerCounter; ///< Used to schedule hemodiafiltration data publication to CAN bus. static BOOL isHDFRateUpdated; ///< flag indicating to update HDF rate needed. static BOOL reachedMaximumHDFVolume; ///< flag indicating that the current HDF volume has reached the maximum volume. static F32 hdfMaxFiltrationFraction; ///< Current HDF maximum filtration fraction. static F32 hdfMaxTMP; ///< Current HDF maximum TMP in mmHg. static F32 hdfMaxRate; ///< Current HDF maximum flow rate in ml/min. static OVERRIDE_U32_T hdfDataPublishInterval; ///< Hemodiafiltration data publish interval. // ********** private function prototypes ********** static HDF_EXEC_STATE_T handleHDFRunningState( void ); static HDF_EXEC_STATE_T handleHDFPausedState( void ); static void UpdateHDFRateAndVolume( void ); static F32 CheckHDFRate( F32 proposed_rate ); static void publishHemodiafiltrationData( void ); /*********************************************************************//** * @brief * The initHemodiafiltration function initializes the hemodiafiltration unit. * @details \b Inputs: none * @details \b Outputs: unit variables initialized * @return none *************************************************************************/ void initHemodiafiltration( void ) { // Initialize substitution pump PI controller to target pressure initializePIController( PI_CONTROLLER_ID_SUB_PUMP_PRES, ZERO_RATE, SUB_PUMP_PRESSURE_CONTROL_P_COEFFICIENT, SUB_PUMP_PRESSURE_CONTROL_I_COEFFICIENT, ZERO_RATE, HDF_MAX_RATE, FALSE, 0 ); hdfExecState = TD_HDF_PAUSED; hdfDataPublishInterval.data = HDF_DATA_PUBLISH_INTERVAL; hdfDataPublishInterval.ovData = HDF_DATA_PUBLISH_INTERVAL; hdfDataPublishInterval.ovInitData = 0; hdfDataPublishInterval.override = OVERRIDE_RESET; isHemodiafiltrationRequested = FALSE; hdfTMPTarget = 0.0; hdfManualRate = 0.0; hdfControlTimerCounter = 0; hdfCurrentRate = 0.0; hdfLastActiveRate = HDF_MAX_RATE; hdfMaximumVolume = 0.0; hdfCurrentVolume = 0.0; reachedMaximumHDFVolume = FALSE; hdfMaxFiltrationFraction = HDF_MAX_FILTRATION_FRACTION; hdfMaxTMP = HDF_MAX_TMP; hdfMaxRate = HDF_MAX_RATE; hdfDataPublicationTimerCounter = 0; isHDFRateUpdated = FALSE; } /*********************************************************************//** * @brief * The transitionToHemodiafiltration function prepares for transition to * hemodiafiltration. * @details \b Inputs: none * @details \b Outputs: isHemodiafiltrationRequested,hdfDataPublicationTimerCounter * @return none *************************************************************************/ void transitionToHemodiafiltration( void ) { initHemodiafiltration(); } /*********************************************************************//** * @brief * The execHDFControl function executes the hemodiafiltration state machine. * @details \b Inputs: hdfExecState * @details \b Outputs: hdfExecState * @details \b Alarm: ALARM_ID_DD_SOFTWARE_FAULT when wrong hemodiafiltration * state invoked. * @details Warning: The compensated HDF should be caclulated first and then * call 'updateHDFRequest' function. * @return current state. *************************************************************************/ U32 execHDFControl( void ) { if ( TRUE == isHDFRateUpdated ) { // Update HDF rate cmdChangeQhdf( hdfCurrentRate ); // this results in a message to DD to change the hdf pump rate isHDFRateUpdated = FALSE; } // execute current hemodiafiltration exec state switch ( hdfExecState ) { case TD_HDF_PAUSED: hdfExecState = handleHDFPausedState(); break; case TD_HDF_RUNNING: hdfExecState = handleHDFRunningState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_HDF_INVALID_EXEC_STATE, hdfExecState ) hdfExecState = TD_HDF_PAUSED; break; } //Publish hemodiafiltration data publishHemodiafiltrationData(); return hdfExecState; } /*********************************************************************//** * @brief * The handleHDFPausedState function handles the hemodiafiltration * paused state. * @details \b Inputs:isHemodiafiltrationRequested * @details \b Outputs: HDF state * @return next HDF state. *************************************************************************/ static HDF_EXEC_STATE_T handleHDFPausedState( void ) { HDF_EXEC_STATE_T state = TD_HDF_PAUSED; if ( FALSE == reachedMaximumHDFVolume && TRUE == isHemodiafiltrationRequested ) { if ( hdfManualRate > ZERO_RATE ) { // manual rate set, use that as the proposed rate hdfCurrentRate = CheckHDFRate( hdfManualRate ); } else { // TMP control, resume with previous HDF rate // TODO: make sure that hdfCurrentRate is not 0 if HDF is actually wanted (volume not > max) hdfCurrentRate = CheckHDFRate( hdfCurrentRate ); if ( hdfCurrentRate > ZERO_RATE ) { resetPIController( PI_CONTROLLER_ID_SUB_PUMP_PRES, hdfCurrentRate, 0.0F ); hdfControlTimerCounter = 0; } } isHDFRateUpdated = TRUE; //Transition to run state state = TD_HDF_RUNNING; } return state; } /*********************************************************************//** * @brief * The handleHDFRunningState function handles the hemodiafiltration * running state. * @details \b Inputs:isHemodiafiltrationRequested * @details \b Outputs: HDF state * @return next HDF state. *************************************************************************/ static HDF_EXEC_STATE_T handleHDFRunningState( void ) { HDF_EXEC_STATE_T state = DD_HDF_RUNNING; if ( TRUE != isHemodiafiltrationRequested ) { hdfCurrentRate = 0.0; isHDFRateUpdated = TRUE; state = TD_HDF_PAUSED; } else { UpdateHDFRateAndVolume(); } return state; } /*********************************************************************//** * @brief * The setHemodiafiltrationParameters function sets the the hemodiafiltration requested * parameters for this treatment. * @details \b Inputs: setHDFTMPTarget, setHDFManualRate, setHDFMaximumVolume * @details \b Outputs: local variables set: hdfTMPTarget, hdfManualRate, setHDFMaximumVolume * @return none. *************************************************************************/ void setHemodiafiltrationParameters( F32 setHDFTMPTarget, F32 setHDFManualRate, F32 setHDFMaximumVolume) { hdfTMPTarget = setHDFTMPTarget; // mmHg hdfManualRate = setHDFManualRate; // ml/min hdfMaximumVolume = setHDFMaximumVolume; // ml } /*********************************************************************//** * @brief * The StartHemodiafiltration function starts or resumes hemodiafiltration flow * @details \b Inputs: None * @details \b Outputs: Hemodiafiltration is running * @return none. *************************************************************************/ void StartHemodiafiltration( void ) { isHemodiafiltrationRequested = TRUE; hdfCurrentRate = hdfLastActiveRate; } /*********************************************************************//** * @brief * The StopHemodiafiltration function stops hemodiafiltration flow * @details \b Inputs: None * @details \b Outputs: Hemodiafiltration is not running * @return none. *************************************************************************/ void StopHemodiafiltration( void ) { isHemodiafiltrationRequested = FALSE; hdfLastActiveRate = hdfCurrentRate; } /*********************************************************************//** * @brief * The CheckHDFRate function checks whether the hemodiafiltration rate * is within the limits: max filtration fraction and max HDF rate * @details \b Inputs: proposed HDF rate, TMP, Qb, Quf, limits * @details \b Outputs: updated HDF rate * @return none. *************************************************************************/ static F32 CheckHDFRate( F32 proposed_rate ) { F32 Qb = getMeasuredBloodFlowRate(); //get blood flow rate F32 Quf = getTDUFRate(); F32 max_sub_rate = ( hdfMaxFiltrationFraction * Qb ) - Quf; if (max_sub_rate > HDF_MAX_RATE) { max_sub_rate = HDF_MAX_RATE; } if (proposed_rate > max_sub_rate) { return max_sub_rate; } else { return proposed_rate; } } /*********************************************************************//** * @brief * The UpdateHDFCompensation function updates the hemodiafiltration rate * based on the trans-membrane-pressure * @details \b Inputs: D41 and H14 temperature * @details \b Outputs: updated HDF rate * @return none. *************************************************************************/ static void UpdateHDFRateAndVolume( void ) { if ( ( ++hdfControlTimerCounter >= HDF_CONTROL_INTERVAL ) ) { // get new rate from PI controller if ( ZERO_RATE == hdfManualRate ) { F32 new_rate = runPIController( PI_CONTROLLER_ID_SUB_PUMP_PRES, hdfTMP_target, getLongFilteredTMPPressure() ); hdfCurrentRate = CheckHDFRate( new_rate ); } // update and check volume hdfCurrentvolume += ((F32) HDF_CONTROL_PERIOD_SECONDS / (F32) SECONDS_PER_MINUTE) * hdfCurrentRate; //ml if ( currentHDFvolume > hdfMaximumVolume ) { reachedMaximumHDFVolume = TRUE; // Done delivering HDF, turn off pump and go to pause state hdfCurrentRate = ZERO_RATE; hdfExecState = TD_HDF_PAUSED; } hdfControlTimerCounter = 0; isHDFRateUpdated = TRUE; //Some rate updates could be avoided by checking new rate against old rate } } /*********************************************************************//** * @brief * The getCurrentHDFExecState function returns the current state * of the hemodiafiltration. * @details \b Inputs: hdfExecState * @details \b Outputs: none * @return the current state of HDF execution state. *************************************************************************/ HDF_EXEC_STATE_T getCurrentHDFExecState( void ) { return hdfExecState; } /*********************************************************************//** * @brief * The publishHemodiafiltrationData function broadcasts the hemodiafiltration * data at defined interval. * @details \b Inputs: hdfDataPublicationTimerCounter * @details \b Outputs: DD hemodiafiltration data broadcast message sent * @details \b Message \Sent: MSG_ID_DD_HDF_DATA to publish the hemodiafiltration * data. * @return none *************************************************************************/ static void publishHemodiafiltrationData( void ) { if ( ++hdfDataPublicationTimerCounter >= getU32OverrideValue( &hdfDataPublishInterval ) ) { HDF_DATA_T data; data.hdfExecState = (U32)hdfExecState; data.hdfRequestedRate = hdfManualRate; data.hdfTargetTMP = hdfTMPTarget; data.hdfCurrentRate = hdfCurrentRate; data.hdfCurrentTMP = getLongFilteredTMPPressure(); data.isHDFRequested = (U32)isHemodiafiltrationRequested; data.hdfRequestedVolume = hdfMaximumVolume; data.hdfTotalVolume = hdfCurrentVolume; broadcastData( MSG_ID_TD_HDF_DATA, COMM_BUFFER_OUT_CAN_TD_BROADCAST, (U08*)&data, sizeof( HDF_DATA_T ) ); hdfDataPublicationTimerCounter = 0; } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testTDHDFDataPublishIntervalOverride function overrides the * DD hemodiafiltration data publish interval. * @details \b Inputs: hdfDataPublishInterval * @details \b Outputs: hdfDataPublishInterval * @param Override message from Dialin which includes the interval * (in ms) to override the TD hemodiafiltration data publish interval to. * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testDDHDFDataPublishIntervalOverride( MESSAGE_T *message ) { BOOL result = u32BroadcastIntervalOverride( message, &hdfDataPublishInterval, TASK_GENERAL_INTERVAL ); return result; } /**@}*/