/************************************************************************** * * 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 StateTxIsolatedUF.c * * @author (last) Jashwant Gantyada * @date (last) 26-May-2026 * * @author (original) Jashwant Gantyada * @date (original) 18-May-2026 * ***************************************************************************/ #include "StateTxIsolatedUF.h" #include "AirTrap.h" #include "BloodFlow.h" #include "DDInterface.h" #include "Messaging.h" #include "ModeTreatment.h" #include "OperationModes.h" #include "StateTxDialysis.h" #include "TaskGeneral.h" #include "Valves.h" /** * @addtogroup StateTxIsolatedUF * @{ */ // ********** private definitions ********** // ********** private data ********** static ISOLATED_UF_STATE_T currentIsolatedUFState; ///< Current isolated UF sub-state. static OVERRIDE_F32_T isolatedUFVolumeDrawnL; ///< Current isolated UF volume drawn from patient in L. static BOOL isolatedUFActive; ///< Flag indicating isolated UF is active. static BOOL isolatedUFCompleted; ///< Flag indicating isolated UF completed. static BOOL ufPauseRequested; ///< Flag indicates UF pause has been requested by user. static BOOL ufResumeRequested; ///< Flag indicates UF resume has been requested by user. static F32 setDialysateFlowMlMin; ///< Dialysate flow rate (Qd) for isolated UF. static U32 isolatedUFElapsedTimerCtr; ///< Elapsed isolated UF time in general task intervals (50 ms per tick). // ********** private function prototypes ********** static void transitionToIsolatedUFState( ISOLATED_UF_STATE_T newState ); static ISOLATED_UF_STATE_T handleIsolatedUFRunningState( void ); static ISOLATED_UF_STATE_T handleIsolatedUFPausedState( void ); static void updateIsolatedUFVolume( void ); /*********************************************************************//** * @brief * The initIsolatedUF function initializes the isolated UF sub-mode module. * @details \b Inputs: none * @details \b Outputs: isolated UF state initialized. * @return none *************************************************************************/ void initIsolatedUF( void ) { currentIsolatedUFState = ISOLATED_UF_RUNNING_STATE; isolatedUFVolumeDrawnL.data = 0.0F; isolatedUFVolumeDrawnL.ovData = 0.0F; isolatedUFVolumeDrawnL.ovInitData = 0.0F; isolatedUFVolumeDrawnL.override = OVERRIDE_RESET; isolatedUFActive = FALSE; isolatedUFCompleted = FALSE; ufPauseRequested = FALSE; ufResumeRequested = FALSE; setDialysateFlowMlMin = 0.0F; isolatedUFElapsedTimerCtr = 0U; } /*********************************************************************//** * @brief * The transitionToIsolatedUF function prepares for transition to isolated UF. * @details \b Inputs: treatment parameters, isolated UF settings * @details \b Outputs: DD command, valves, blood pump, current sub-state * @return none *************************************************************************/ void transitionToIsolatedUF( void ) { F32 dialTemp = getTreatmentParameterF32( TREATMENT_PARAM_DIALYSATE_TEMPERATURE ); F32 ufRateMlMin = getIsolatedUFRateMlMin(); F32 acidConvFactor = getTreatmentParameterF32( TREATMENT_PARAM_ACID_CONCENTRATE_CONV_FACTOR ); F32 bicarbConvFactor = BICARBONATE_CONVERSION_FACTOR; U32 sodium = getTreatmentParameterU32( TREATMENT_PARAM_SODIUM ); U32 bicarbonate = getTreatmentParameterU32( TREATMENT_PARAM_BICARBONATE ); doorClosedRequired( TRUE ); // Set user alarm recovery actions allowed in this sub-mode. setAlarmUserActionEnabled( ALARM_USER_ACTION_RESUME, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_RINSEBACK, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_END_TREATMENT, TRUE ); // Mid-treatment isolated UF keeps dialysate generation at the treatment rate // while bypassing the dialyzer. Post-treatment and standalone isolated UF do not generate dialysate. if ( TRUE == isIsolatedUFMidTreatment() ) { setDialysateFlowMlMin = (F32)getTreatmentParameterU32( TREATMENT_PARAM_DIALYSATE_FLOW ); } else { setDialysateFlowMlMin = 0.0F; } cmdStartGenerateDialysate( setDialysateFlowMlMin, ufRateMlMin, dialTemp, TRUE, acidConvFactor, bicarbConvFactor, sodium, bicarbonate ); transitionToIsolatedUFState( currentIsolatedUFState ); if ( FALSE == isolatedUFActive ) { isolatedUFElapsedTimerCtr = 0U; isolatedUFCompleted = FALSE; isolatedUFActive = TRUE; } setCurrentSubState( (U32)currentIsolatedUFState ); } /*********************************************************************//** * @brief * The transitionToIsolatedUFState function sets actuators for the isolated UF * sub-state. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if invalid state is given. * @details \b Inputs: newState * @details \b Outputs: blood pump, valves, air trap control, DD Quf * @param newState Isolated UF state to transition to. * @return none *************************************************************************/ static void transitionToIsolatedUFState( ISOLATED_UF_STATE_T newState ) { switch ( newState ) { case ISOLATED_UF_RUNNING_STATE: setValvePosition( H1_VALV, VALVE_POSITION_B_OPEN ); setValvePosition( H19_VALV, VALVE_POSITION_B_OPEN ); setBloodPumpTargetFlowRate( getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ), MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_CLOSED_LOOP ); cmdChangeQuf( getIsolatedUFRateMlMin() ); startAirTrapControl(); break; case ISOLATED_UF_PAUSED_STATE: setValvePosition( H1_VALV, VALVE_POSITION_B_OPEN ); setValvePosition( H19_VALV, VALVE_POSITION_B_OPEN ); setBloodPumpTargetFlowRate( getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ), MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_CLOSED_LOOP ); cmdChangeQuf( 0.0F ); startAirTrapControl(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_MODE_TREATMENT_INVALID_STATE, (U32)newState ) break; } } /*********************************************************************//** * @brief * The getCurrentIsolatedUFState function gets the current isolated UF state. * @details \b Inputs: currentIsolatedUFState * @details \b Outputs: none * @return currentIsolatedUFState *************************************************************************/ ISOLATED_UF_STATE_T getCurrentIsolatedUFState( void ) { return currentIsolatedUFState; } /*********************************************************************//** * @brief * The getIsolatedUFVolumeDrawn function gets the total isolated UF volume * drawn from the patient so far. * @details \b Inputs: isolatedUFVolumeDrawnL * @details \b Outputs: none * @return Isolated UF volume drawn in L. *************************************************************************/ F32 getIsolatedUFVolumeDrawn( void ) { return getF32OverrideValue( &isolatedUFVolumeDrawnL ); } /*********************************************************************//** * @brief * The isIsolatedUFCompleted function determines whether isolated UF completed. * @details \b Inputs: isolatedUFCompleted * @details \b Outputs: none * @return TRUE if isolated UF completed, FALSE otherwise. *************************************************************************/ BOOL isIsolatedUFCompleted( void ) { return isolatedUFCompleted; } /*********************************************************************//** * @brief * The endIsolatedUFOnUserStop function ends an active isolated UF session. * @details \b Inputs: none * @details \b Outputs: isolatedUFActive, isolatedUFCompleted cleared * @return none *************************************************************************/ void endIsolatedUFOnUserStop( void ) { isolatedUFActive = FALSE; isolatedUFCompleted = FALSE; } /*********************************************************************//** * @brief * The handleUFPauseResumeRequest function routes a UI pause/resume UF request * to the dialysis or isolated UF sub-mode handler. * @details \b Inputs: getTreatmentState * @details \b Outputs: none * @param message Pause/resume request message from UI. * @return TRUE if request was accepted, FALSE otherwise. *************************************************************************/ BOOL handleUFPauseResumeRequest( MESSAGE_T *message ) { BOOL result; if ( TREATMENT_ISO_UF_STATE == getTreatmentState() ) { result = signalPauseResumeIsolatedUF( message ); } else { result = signalPauseResumeUF( message ); } return result; } /*********************************************************************//** * @brief * The signalPauseResumeIsolatedUF function handles a request to pause or resume * isolated ultrafiltration. * @details \b Message \b Sent: MSG_ID_TD_UF_PAUSE_RESUME_RESPONSE * @details \b Inputs: currentIsolatedUFState * @details \b Outputs: ufPauseRequested, ufResumeRequested * @param message Message from UI which includes a flag indicating whether * to pause or resume ultrafiltration. * @return TRUE if request was accepted, FALSE otherwise. *************************************************************************/ BOOL signalPauseResumeIsolatedUF( MESSAGE_T *message ) { TREATMENT_STATE_T trtState = getTreatmentState(); TD_OP_MODE_T currMode = getCurrentOperationMode(); UI_RESPONSE_PAYLOAD_T response; response.accepted = FALSE; response.rejectionReason = REQUEST_REJECT_REASON_NONE; if ( sizeof( BOOL ) == message->hdr.payloadLen ) { BOOL payload; memcpy( &payload, message->payload, sizeof( BOOL ) ); if ( TRUE == payload ) { if ( ( MODE_TREA == currMode ) && ( TREATMENT_ISO_UF_STATE == trtState ) && ( ISOLATED_UF_PAUSED_STATE == currentIsolatedUFState ) ) { ufResumeRequested = TRUE; response.accepted = TRUE; } else { if ( MODE_TREA != currMode ) { response.rejectionReason = REQUEST_REJECT_REASON_NOT_IN_TREATMENT_MODE; } else if ( TREATMENT_ISO_UF_STATE != trtState ) { response.rejectionReason = REQUEST_REJECT_REASON_INVALID_TREATMENT_STATE; } else { response.rejectionReason = REQUEST_REJECT_REASON_UF_NOT_PAUSED; } } } else { if ( ( MODE_TREA == currMode ) && ( TREATMENT_ISO_UF_STATE == trtState ) && ( ISOLATED_UF_RUNNING_STATE == currentIsolatedUFState ) ) { ufPauseRequested = TRUE; response.accepted = TRUE; } else { if ( MODE_TREA != currMode ) { response.rejectionReason = REQUEST_REJECT_REASON_NOT_IN_TREATMENT_MODE; } else if ( TREATMENT_ISO_UF_STATE != trtState ) { response.rejectionReason = REQUEST_REJECT_REASON_INVALID_TREATMENT_STATE; } else { response.rejectionReason = REQUEST_REJECT_REASON_UF_NOT_IN_PROGESS; } } } } else { response.rejectionReason = REQUEST_REJECT_REASON_PARAM_OUT_OF_RANGE; } sendMessage( MSG_ID_TD_UF_PAUSE_RESUME_RESPONSE, COMM_BUFFER_OUT_CAN_TD_2_UI, (U08*)(&response), sizeof( UI_RESPONSE_PAYLOAD_T ) ); return response.accepted; } /*********************************************************************//** * @brief * The execIsolatedUF function executes the isolated UF sub-mode state machine. * @details \b Message \b Sent: TD_EVENT_SUB_STATE_CHANGE when state changes * @details \b Inputs: currentIsolatedUFState * @details \b Outputs: currentIsolatedUFState * @return none *************************************************************************/ void execIsolatedUF( void ) { ISOLATED_UF_STATE_T priorSubState = currentIsolatedUFState; switch ( currentIsolatedUFState ) { case ISOLATED_UF_RUNNING_STATE: currentIsolatedUFState = handleIsolatedUFRunningState(); break; case ISOLATED_UF_PAUSED_STATE: currentIsolatedUFState = handleIsolatedUFPausedState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_MODE_TREATMENT_INVALID_STATE, (U32)currentIsolatedUFState ) break; } updateIsolatedUFVolume(); if ( priorSubState != currentIsolatedUFState ) { setCurrentSubState( (U32)currentIsolatedUFState ); SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_SUB_STATE_CHANGE, priorSubState, currentIsolatedUFState ); } } /*********************************************************************//** * @brief * The handleIsolatedUFRunningState function handles isolated UF running state. * @details \b Inputs: isolatedUFActive, ufPauseRequested, isolatedUFElapsedTimerCtr * @details \b Outputs: isolatedUFElapsedTimerCtr, isolatedUFCompleted, isolatedUFActive * @return current isolated UF state. *************************************************************************/ static ISOLATED_UF_STATE_T handleIsolatedUFRunningState( void ) { ISOLATED_UF_STATE_T result = ISOLATED_UF_RUNNING_STATE; U32 durationTimerLimit = ( getIsolatedUFDurationMin() * SEC_PER_MIN * MS_PER_SECOND ) / TASK_GENERAL_INTERVAL; F32 setVolumeL = getIsolatedUFSetVolumeMl() / (F32)ML_PER_LITER; if ( TRUE == ufPauseRequested ) { ufPauseRequested = FALSE; transitionToIsolatedUFState( ISOLATED_UF_PAUSED_STATE ); result = ISOLATED_UF_PAUSED_STATE; } else if ( TRUE == isolatedUFActive ) { isolatedUFElapsedTimerCtr++; if ( ( ( durationTimerLimit > 0U ) && ( isolatedUFElapsedTimerCtr >= durationTimerLimit ) ) || ( ( setVolumeL > 0.0F ) && ( getIsolatedUFVolumeDrawn() >= setVolumeL ) ) ) { isolatedUFCompleted = TRUE; isolatedUFActive = FALSE; } } return result; } /*********************************************************************//** * @brief * The handleIsolatedUFPausedState function handles isolated UF paused state. * @details \b Inputs: ufResumeRequested * @details \b Outputs: ufResumeRequested * @return current isolated UF state. *************************************************************************/ static ISOLATED_UF_STATE_T handleIsolatedUFPausedState( void ) { ISOLATED_UF_STATE_T result = ISOLATED_UF_PAUSED_STATE; if ( TRUE == ufResumeRequested ) { ufResumeRequested = FALSE; transitionToIsolatedUFState( ISOLATED_UF_RUNNING_STATE ); result = ISOLATED_UF_RUNNING_STATE; } return result; } /*********************************************************************//** * @brief * The updateIsolatedUFVolume function updates measured isolated UF volume. * @details \b Inputs: none * @details \b Outputs: isolatedUFVolumeDrawnL * @return none *************************************************************************/ static void updateIsolatedUFVolume( void ) { isolatedUFVolumeDrawnL.data = 0.0F; // TODO: update when DD reports delivered UF volume. } /**@}*/