Index: firmware/App/Controllers/HemoDiaFiltration.c =================================================================== diff -u --- firmware/App/Controllers/HemoDiaFiltration.c (revision 0) +++ firmware/App/Controllers/HemoDiaFiltration.c (revision 1e0e209cd649584ea88b450b36558b2de63c60e2) @@ -0,0 +1,396 @@ +/************************************************************************** +* +* 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; +} + + +/**@}*/ Index: firmware/App/Controllers/HemoDiaFiltration.h =================================================================== diff -u --- firmware/App/Controllers/HemoDiaFiltration.h (revision 0) +++ firmware/App/Controllers/HemoDiaFiltration.h (revision 1e0e209cd649584ea88b450b36558b2de63c60e2) @@ -0,0 +1,61 @@ +/************************************************************************** +* +* 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.h +* +* @author (last) Steve Jarpe +* @date (last) 23-May-2026 +* +* @author (original) Steve Jarpe +* @date (original) 23-May-2026 +* +***************************************************************************/ + +#ifndef __HEMO_DIAFILTRATION_H__ +#define ___HEMO_DIAFILTRATION_H__ + +#include "TDCommon.h" +#include "TDDefs.h" + +/** + * @defgroup Hemodiafiltration Hemodiafiltration + * @brief Hemodiafiltration control unit. Performs hemodiafiltration functions via a state machine. + * + * @addtogroup Hemodiafiltration + * @{ + */ + +// ********** public definitions ********** + +/// hemodiafiltration data structure +typedef struct +{ + U32 hdfExecState; ///< Hemodiafiltration execution state + F32 hdfRequestedRate; ///< Hemodiafiltration requested manual rate + F32 hdfTargetTMP; ///< Hemodiafiltration requested target TMP + F32 hdfCurrentRate; ///< Hemodiafiltration current set rate + F32 hdfCurrentTMP; ///< Hemodiafiltration current TMP (trans-membrane pressure) + F32 hdfRequestedVolume; ///< Hemodiafiltration requested maximum volume + F32 hdfTotalVolume; ///< Hemodiafiltration total volume delivered + U32 isHDFRequested; ///< Hemodiafiltration run or pause request +} HDF_DATA_T; + +// ********** public function prototypes ********** + +void initHemodiafiltration( void ); // Initialize hemodiafiltration unit +void transitionToHemodiafiltration( void ); // Prepares for transition to hemodiafiltration execution +U32 execHDFControl( void ); // Execute the hemodiafiltration state machine +HDF_EXEC_STATE_T getCurrentHDFExecState( void ); // Get the current state of the hemodifiltration execution + +BOOL testTDHDFDataPublishIntervalOverride( MESSAGE_T *message ); // To override the HDF data publish interval +void setHemodiafiltrationParameters( F32 setHDFTMPTarget, F32 setHDFManualRate, F32 setHDFMaximumVolume); +void StartHemodiafiltration( void ); +void StopHemodiafiltration( void ); + +/**@}*/ + +#endif Index: firmware/App/Services/DDInterface.c =================================================================== diff -u -rda59fa4a98dbc11c37677e92a66aa940d251678f -r1e0e209cd649584ea88b450b36558b2de63c60e2 --- firmware/App/Services/DDInterface.c (.../DDInterface.c) (revision da59fa4a98dbc11c37677e92a66aa940d251678f) +++ firmware/App/Services/DDInterface.c (.../DDInterface.c) (revision 1e0e209cd649584ea88b450b36558b2de63c60e2) @@ -114,6 +114,7 @@ dialysateDeliveryCmdSet.start = FALSE; dialysateDeliveryCmdSet.dialRate = 0.0F; dialysateDeliveryCmdSet.ufRate = 0.0F; + dialysateDeliveryCmdSet.hdfRate = 0.0F; dialysateDeliveryCmdSet.dialTemp = 0.0F; dialysateDeliveryCmdSet.bypassDialyzer = TRUE; dialysateDeliveryCmdSet.acidConvFactor = 0.0F; @@ -381,6 +382,7 @@ * @details \b Message \b Sent: Start/continue generate dialysate command. * @param qd Target dialysate flow rate (Qd). * @param quf Target ultrafiltration rate (Quf) (in mL/min). + * @param qhdf Hemodiafiltration rate (Qhdf) (in mL/min). * @param dialTemp Target dialysate temperature in deg C. * @param bypass Flag indicating whether dialyzer should be bypassed. * @param acidConvFactor Conversion factor for the acid used. @@ -389,11 +391,12 @@ * @param bicarbonate Level of bicarbonate used in mEq/L. * @return none *************************************************************************/ -void cmdStartGenerateDialysate( F32 qd, F32 quf, F32 dialTemp, BOOL bypass, F32 acidConvFactor, F32 bicarbConvFactor, U32 sodium, U32 bicarbonate ) +void cmdStartGenerateDialysate( F32 qd, F32 quf, F32 qhdf, F32 dialTemp, BOOL bypass, F32 acidConvFactor, F32 bicarbConvFactor, U32 sodium, U32 bicarbonate ) { dialysateDeliveryCmdSet.start = TRUE; dialysateDeliveryCmdSet.dialRate = qd; dialysateDeliveryCmdSet.ufRate = quf; + dialysateDeliveryCmdSet.hdfRate = qhdf; dialysateDeliveryCmdSet.dialTemp = dialTemp; dialysateDeliveryCmdSet.bypassDialyzer = bypass; dialysateDeliveryCmdSet.acidConvFactor = acidConvFactor; @@ -459,6 +462,31 @@ /*********************************************************************//** * @brief + * The cmdChangeQhdf function sends a generate dialysate command to the DD + * with a given new hemodiafiltration rate. + * @details \b Inputs: none + * @details \b Outputs: dialysateDeliveryCmdSet + * @details \b Message \b Sent: Continue generate dialysate command w/ new Quf. + * @param qhdf, hemodiafiltration flow rate (in mL/min). + * @return none + *************************************************************************/ +void cmdChangeQhdf( F32 qhdf ) +{ + if ( TRUE == dialysateDeliveryCmdSet.start ) + { + dialysateDeliveryCmdSet.hdfRate = qhdf; +#ifndef TEST_UI_ONLY + sendMessage( MSG_ID_DD_GEN_DIALYSATE_REQUEST_DATA, COMM_BUFFER_OUT_CAN_TD_2_DD, (U08*)(&dialysateDeliveryCmdSet), sizeof( DIALYSATE_DELIVERY_REQ_PAYLOAD_T ) ); +#endif + } + else + { + // TODO - s/w fault? + } +} + +/*********************************************************************//** + * @brief * The cmdBypassDialyzer function sends a generate dialysate command to the DD * with a given flag indicating whether dialyzer should be bypassed. * @details \b Inputs: none Index: firmware/App/Services/DDInterface.h =================================================================== diff -u -rda59fa4a98dbc11c37677e92a66aa940d251678f -r1e0e209cd649584ea88b450b36558b2de63c60e2 --- firmware/App/Services/DDInterface.h (.../DDInterface.h) (revision da59fa4a98dbc11c37677e92a66aa940d251678f) +++ firmware/App/Services/DDInterface.h (.../DDInterface.h) (revision 1e0e209cd649584ea88b450b36558b2de63c60e2) @@ -58,9 +58,10 @@ BOOL setDialysatePressure( MESSAGE_T *message ); void cmdStartPreGenerateDialysate( F32 qd, F32 dialTemp, F32 acidConvFactor, F32 bicarbConvFactor, U32 sodium, U32 bicarbonate ); -void cmdStartGenerateDialysate( F32 qd, F32 quf, F32 dialTemp, BOOL bypass, F32 acidConvFactor, F32 bicarbConvFactor, U32 sodium, U32 bicarbonate ); +void cmdStartGenerateDialysate( F32 qd, F32 quf, F32 qhdf, F32 dialTemp, BOOL bypass, F32 acidConvFactor, F32 bicarbConvFactor, U32 sodium, U32 bicarbonate ); void cmdChangeQd( F32 qd ); void cmdChangeQuf( F32 quf ); +void cmdChangeQhdf( F32 qhdf ); void cmdBypassDialyzer( BOOL bypass ); void cmdStopGenerateDialysate( void );