/************************************************************************** * * 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 StateTxRecirc.c * * @author (last) Praneeth Bunne * @date (last) 29-Jun-2026 * * @author (original) Praneeth Bunne * @date (original) 29-Jun-2026 * ***************************************************************************/ #include "AirTrap.h" #include "BloodFlow.h" #include "DDInterface.h" #include "Messaging.h" #include "ModeTreatment.h" #include "OperationModes.h" #include "StateTxRecirc.h" #include "SyringePump.h" #include "TaskGeneral.h" #include "Timers.h" #include "Valves.h" /** * @addtogroup StateTxRecirc * @{ */ // ********** private definitions ********** /// Target flow rate for re-circulation of saline on blood-side circuit. #define RECIRC_BP_FLOW_RATE_ML_MIN 100 /// Re-circulation status broadcast interval. #define RECIRC_DATA_PUBLISH_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) /// Conversion from minutes to task ticks #define MIN_TO_TICKS( min ) ( min * SEC_PER_MIN * ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ) /// Recirc warning before 10 mins from timeout #define RECIRC_WARNING_LEAD_TIME_MIN 10U // ********** private data ********** static TREATMENT_RECIRC_STATE_T currentRecircState; ///< Current state of the treatment re-circulate sub-mode. static U32 recircTimerCtr; ///< Timer counter for time spent in re-circ sub-mode. static U32 recircPublishTimerCtr; ///< Timer counter for next status broadcast. static U32 recircWarningTicks; ///< Timeout Warning threshold (in ticks). static U32 recircTimeoutTicks; ///< Timeout threshold (in ticks). static BOOL startRecircRequested; ///< Flag indicates user requesting to start re-circulating. static BOOL reconnectRequested; ///< Flag indicates user requesting to stop re-circulating and prepare for patient reconnect. static BOOL backToTreatmentRequested; ///< Flag indicates user confirmed reconnected, requesting to return to treatment. static BOOL resumeRecircRequested; ///< Flag indicates user requesting resumption of re-circulation from stopped state. static BOOL recircWarningAlarmFired; ///< Flag indicates warning alarm fired. static BOOL recircTimeoutAlarmFired; ///< Flag indicates timeout alarm fired. // ********** private function prototypes ********** static void resetTreatmentRecircFlags( void ); static void setupForRecirculationState( void ); static void setupForRecirculationStopState( void ); static void publishTreatmentRecircData( void ); static void handleRecircTimeouts( void ); static TREATMENT_RECIRC_STATE_T handleRecircDisconnectPatientState( void ); static TREATMENT_RECIRC_STATE_T handleRecircRecircState( void ); static TREATMENT_RECIRC_STATE_T handleRecircStoppedState( void ); static TREATMENT_RECIRC_STATE_T handleRecircReconnectPatientState( void ); static BOOL handleRecircConfirmDisconnectUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ); static BOOL handleRecircResumeUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ); static BOOL handleRecircReconnectUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ); static BOOL handleRecircConfirmReconnectUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ); /*********************************************************************//** * @brief * The initTreatmentRecirc function initializes the Treatment Re-circulate * sub-mode module. * @details Inputs: none * @details Outputs: Treatment Re-circulate sub-mode module initialized. * @return none *************************************************************************/ void initTreatmentRecirc( void ) { currentRecircState = TREATMENT_RECIRC_DISCONNECT_PATIENT_STATE; recircTimerCtr = 0; recircPublishTimerCtr = RECIRC_DATA_PUBLISH_INTERVAL - 9; recircWarningAlarmFired = FALSE; recircTimeoutAlarmFired = FALSE; // TODO: Replace getSysConfigTreatmentParameterU32DefaultValue() with getNVRecord2Driver() when implemented recircWarningTicks = MIN_TO_TICKS( ( getSysConfigTreatmentParameterU32DefaultValue( TREATMENT_PARAM_TEMPORARY_BREAK_RECIRC_TIMEOUT ) ) - ( RECIRC_WARNING_LEAD_TIME_MIN ) ); recircTimeoutTicks = MIN_TO_TICKS( getSysConfigTreatmentParameterU32DefaultValue( TREATMENT_PARAM_TEMPORARY_BREAK_RECIRC_TIMEOUT ) ); resetTreatmentRecircFlags(); } /*********************************************************************//** * @brief * The resetTreatmentRecircFlags function resets all re-circ request flags. * @details Inputs: none * @details Outputs: all request flags set to FALSE. * @return none *************************************************************************/ static void resetTreatmentRecircFlags( void ) { startRecircRequested = FALSE; reconnectRequested = FALSE; backToTreatmentRequested = FALSE; resumeRecircRequested = FALSE; } /*********************************************************************//** * @brief * The transitionToTreatmentRecirc function prepares for transition to * Treatment Re-circulate sub-mode. * @details Inputs: none * @details Outputs: none * @return none *************************************************************************/ void transitionToTreatmentRecirc( void ) { initTreatmentRecirc(); setCurrentSubState( (U32)currentRecircState ); // Set alarm actions setAlarmUserActionEnabled( ALARM_USER_ACTION_RESUME, TRUE); setAlarmUserActionEnabled( ALARM_USER_ACTION_RINSEBACK, FALSE); setAlarmUserActionEnabled( ALARM_USER_ACTION_END_TREATMENT, TRUE); // Set actuators for disconnect patient state setupForRecirculationStopState(); // Bypass dialyzer cmdBypassDialyzer( TRUE ); // Stop syringe pump and air trap control stopSyringePump(); endAirTrapControl(); } /*********************************************************************//** * @brief * The setupForRecirculationState function configures actuators for the * active re-circulation (Recirc) state. * @details Inputs: none * @details Outputs: none * @return none *************************************************************************/ static void setupForRecirculationState( void ) { // Open arterail and venous lines setValvePosition( H1_VALV, VALVE_POSITION_B_OPEN ); setValvePosition( H19_VALV, VALVE_POSITION_B_OPEN ); // Start blood pump at re-circulation flow rate setBloodPumpTargetFlowRate( RECIRC_BP_FLOW_RATE_ML_MIN, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_CLOSED_LOOP ); } /*********************************************************************//** * @brief * The setupForRecirculationStopState function configures actuators for * Disconnect Patient, Stopped, Reconnect Patient states. * @details Inputs: none * @details Outputs: none * @return none *************************************************************************/ static void setupForRecirculationStopState( void ) { // Stop blood pump signalBloodPumpHardStop(); // Close arterail and venous lines setValvePosition( H1_VALV, VALVE_POSITION_C_CLOSE ); setValvePosition( H19_VALV, VALVE_POSITION_C_CLOSE ); } /*********************************************************************//** * @brief * The handleRecircTimeouts function checks total time in re-circulate * sub-mode and fires warning alarm and timeout alarm at 20 min and 30 min respectively. * Only fires each alarm once per re-circulate session. * @details \b Alarms: ALARM_ID_TD_RECIRC_TIMEOUT_WARNING if 10 mins left from timeout, * ALARM_ID_TD_RECIRC_TIMEOUT if recirculation timeout * @details Inputs: recircTimerCtr, recircWarningTicks, recircTimeoutTicks, recircWarningAlarmFired * @details Outputs: recircWarningAlarmFired, recircTimeoutAlarmFired * @return none *************************************************************************/ static void handleRecircTimeouts( void ) { if ( ( recircTimerCtr >= recircWarningTicks ) && ( FALSE == recircWarningAlarmFired ) ) { recircWarningAlarmFired = TRUE; SET_ALARM_WITH_1_U32_DATA( ALARM_ID_TD_RECIRC_TIMEOUT_WARNING, (U32)recircTimerCtr ); } if ( ( recircTimerCtr >= recircTimeoutTicks ) && ( FALSE == recircTimeoutAlarmFired ) ) { recircTimeoutAlarmFired = TRUE; SET_ALARM_WITH_1_U32_DATA( ALARM_ID_TD_RECIRC_TIMEOUT, (U32)recircTimerCtr ); } } /*********************************************************************//** * @brief * The execTreatmentRecirc function executes the Treatment Re-circulate * sub-mode state machine. * @details Inputs: currentRecircState * @details Outputs: currentRecircState, recircTimerCtr * @return none *************************************************************************/ void execTreatmentRecirc( void ) { TREATMENT_RECIRC_STATE_T priorState = currentRecircState; // Count time in this sub-mode recircTimerCtr++; // Check and fire timeout alarms (applies to all sub-states) handleRecircTimeouts(); switch( currentRecircState ) { case TREATMENT_RECIRC_DISCONNECT_PATIENT_STATE: currentRecircState = handleRecircDisconnectPatientState(); break; case TREATMENT_RECIRC_RECIRC_STATE: currentRecircState = handleRecircRecircState(); break; case TREATMENT_RECIRC_STOPPED_STATE: currentRecircState = handleRecircStoppedState(); break; case TREATMENT_RECIRC_RECONNECT_PATIENT_STATE: currentRecircState = handleRecircReconnectPatientState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_TREATMENT_RECIRC_INVALID_STATE, (U32)currentRecircState ); break; } if ( priorState != currentRecircState ) { setCurrentSubState( (U32)currentRecircState ); SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_SUB_STATE_CHANGE, (U32)priorState, (U32)currentRecircState ); } // Re-circulate flags should be handled by now - reset in case not handled by current state resetTreatmentRecircFlags(); // Broadcast recirc status publishTreatmentRecircData(); } /*********************************************************************//** * @brief * The handleRecircDisconnectPatientState function handles the Disconnect * Patient state of Re-circ sub mode. Waits for user to confirm patient * has been disconnected before starting re-circulation. * @details Inputs: startRecircRequested * @details Outputs: none * @return next Treatment re-circulate state *************************************************************************/ static TREATMENT_RECIRC_STATE_T handleRecircDisconnectPatientState( void ) { TREATMENT_RECIRC_STATE_T state = TREATMENT_RECIRC_DISCONNECT_PATIENT_STATE; // User requested to start re-circ? if ( TRUE == startRecircRequested ) { setupForRecirculationState(); state = TREATMENT_RECIRC_RECIRC_STATE; } return state; } /*********************************************************************//** * @brief * The handleRecircRecircState function handles the active Re-circulate * state of Re-circ sub mode. * @details Inputs: reconnectRequested * @details Outputs: none * @return next Treatment re-circulate state *************************************************************************/ static TREATMENT_RECIRC_STATE_T handleRecircRecircState( void ) { TREATMENT_RECIRC_STATE_T state = TREATMENT_RECIRC_RECIRC_STATE; // Alarm indicates stop? if ( TRUE == doesAlarmStatusIndicateStop() ) { setupForRecirculationStopState(); state = TREATMENT_RECIRC_STOPPED_STATE; } // User requested resume treatment? else if ( TRUE == reconnectRequested ) { setupForRecirculationStopState(); state = TREATMENT_RECIRC_RECONNECT_PATIENT_STATE; } else { // Continue recirculation. } return state; } /*********************************************************************//** * @brief * The handleRecircStoppedState function handles the Stopped state of * Re-circ sub mode. * @details Inputs: resumeRecircRequested * @details Outputs: none * @return next Treatment re-circulate state *************************************************************************/ static TREATMENT_RECIRC_STATE_T handleRecircStoppedState( void ) { TREATMENT_RECIRC_STATE_T state = TREATMENT_RECIRC_STOPPED_STATE; // User requested resume re-circ? if ( TRUE == resumeRecircRequested ) { setupForRecirculationState(); state = TREATMENT_RECIRC_RECIRC_STATE; } return state; } /*********************************************************************//** * @brief * The handleRecircReconnectPatientState function handles the Reconnect * Patient state of Re-circ sub mode. * @details Inputs: backToTreatmentRequested * @details Outputs: none * @return next Treatment re-circulate state *************************************************************************/ static TREATMENT_RECIRC_STATE_T handleRecircReconnectPatientState( void ) { TREATMENT_RECIRC_STATE_T state = TREATMENT_RECIRC_RECONNECT_PATIENT_STATE; // User requested resume treatment? if ( TRUE == backToTreatmentRequested ) { signalGoToBloodPrime(); } return state; } /*********************************************************************//** * @brief * The signalTreatmentRecircUserAction function signals a re-circ user action * has been requested. The request is handled and responded to. * @details Inputs: none * @details Outputs: none * @param message UI message which has requested user action * @return TRUE if request is accepted, FALSE if rejected. *************************************************************************/ BOOL signalTreatmentRecircUserAction( MESSAGE_T *message ) { BOOL result = FALSE; U32 cmd = 0; REQUEST_REJECT_REASON_CODE_T rejReason = REQUEST_REJECT_REASON_NONE; UI_RESPONSE_PAYLOAD_T response; if ( sizeof(U32) == message->hdr.payloadLen ) { memcpy( &cmd, message->payload, sizeof(U32) ); if ( FALSE == isAnyAlarmActive() ) { switch ( cmd ) { case REQUESTED_USER_ACTION_TX_RECIRC_CONFIRM_DISCONNECT: result = handleRecircConfirmDisconnectUserAction( &rejReason ); break; case REQUESTED_USER_ACTION_TX_RECIRC_RECONNECT: result = handleRecircReconnectUserAction( &rejReason ); break; case REQUESTED_USER_ACTION_TX_RECIRC_RESUME_RC: result = handleRecircResumeUserAction( &rejReason ); break; case REQUESTED_USER_ACTION_TX_RECIRC_CONFIRM_RECONNECT: result = handleRecircConfirmReconnectUserAction( &rejReason ); break; case REQUESTED_USER_ACTION_TX_RECIRC_END_TREATMENT: // Send message to UI to get user confirmation to end treatment - action initiated only upon receipt of user confirmation from UI addConfirmationRequest( GENERIC_CONFIRM_ID_TREATMENT_END, GENERIC_CONFIRM_CMD_REQUEST_OPEN, 0U ); result = TRUE; break; default: rejReason = REQUEST_REJECT_REASON_INVALID_COMMAND; break; } } else { rejReason = REQUEST_REJECT_REASON_ALARM_IS_ACTIVE; } } else { rejReason = REQUEST_REJECT_REASON_INVALID_REQUEST_FORMAT; } // Respond to user action request response.accepted = result; response.rejectionReason = rejReason; sendMessage( MSG_ID_TD_RECIRCULATE_RESPONSE, COMM_BUFFER_OUT_CAN_TD_2_UI, (U08*)&response, sizeof( UI_RESPONSE_PAYLOAD_T ) ); return result; } /*********************************************************************//** * @brief * The handleRecircConfirmDisconnectUserAction function handles a confirm * patient disconnect user action request. It is assumed that the calling * function will set the reject reason parameter to None beforehand. * @details Inputs: currentRecircState * @details Outputs: startRecircRequested * @param rejReason Code indicating reason for rejection * @return TRUE if user action accepted, FALSE if not *************************************************************************/ static BOOL handleRecircConfirmDisconnectUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ) { BOOL result = FALSE; if ( TREATMENT_RECIRC_DISCONNECT_PATIENT_STATE == currentRecircState ) { startRecircRequested = TRUE; result = TRUE; } else { *rejReason = REQUEST_REJECT_REASON_ACTION_DISABLED_IN_CURRENT_STATE; } return result; } /*********************************************************************//** * @brief * The handleRecircResumeUserAction function handles a re-circulate resume * user action request. It is assumed that the calling function will set * the reject reason parameter to None beforehand. * @details Inputs: currentRecircState * @details Outputs: resumeRecircRequested * @param rejReason Code indicating reason for rejection * @return TRUE if user action accepted, FALSE if not *************************************************************************/ static BOOL handleRecircResumeUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ) { BOOL result = FALSE; if ( TREATMENT_RECIRC_STOPPED_STATE == currentRecircState ) { resumeRecircRequested = TRUE; result = TRUE; } else { *rejReason = REQUEST_REJECT_REASON_ACTION_DISABLED_IN_CURRENT_STATE; } return result; } /*********************************************************************//** * @brief * The handleRecircReconnectUserAction function handles a re-circulate re-connect * user action request. It is assumed that the calling function will set * the reject reason parameter to None beforehand. * @details Inputs: currentRecircState * @details Outputs: reconnectRequested * @param rejReason Code indicating reason for rejection * @return TRUE if user action accepted, FALSE if not *************************************************************************/ static BOOL handleRecircReconnectUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ) { BOOL result = FALSE; if ( TREATMENT_RECIRC_RECIRC_STATE == currentRecircState ) { reconnectRequested = TRUE; result = TRUE; } else { *rejReason = REQUEST_REJECT_REASON_ACTION_DISABLED_IN_CURRENT_STATE; } return result; } /*********************************************************************//** * @brief * The handleRecicConfirmReconnectUserAction function handles a re-circulate * back to treatment user action request. It is assumed that the calling * function will set the reject reason parameter to None beforehand. * @details Inputs: currentRecircState * @details Outputs: backToTreatmentRequested * @param rejReason Code indicating reason for rejection * @return TRUE if user action accepted, FALSE if not *************************************************************************/ static BOOL handleRecircConfirmReconnectUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ) { BOOL result = FALSE; if ( TREATMENT_RECIRC_RECONNECT_PATIENT_STATE != currentRecircState ) { *rejReason = REQUEST_REJECT_REASON_ACTION_DISABLED_IN_CURRENT_STATE; } else if ( FALSE == isRecircAllowed() ) { *rejReason = REQUEST_REJECT_REASON_ACTION_DISABLED_IN_CURRENT_STATE; } else { backToTreatmentRequested = TRUE; result = TRUE; } return result; } /*********************************************************************//** * @brief * The getCurrentTreatmentRecircState function returns the current state * of the treatment re-circulate sub-mode. * @details Inputs: currentRecircState * @details Outputs: none * @return treatmentRecircState *************************************************************************/ TREATMENT_RECIRC_STATE_T getCurrentTreatmentRecircState( void ) { return currentRecircState; } /*********************************************************************//** * @brief * The publishTreatmentRecircData function broadcasts re-circulate countdown * data to the UI at 1 Hz. * @details Inputs: recircPublishTimerCtr, recircTimerCtr * @details Outputs: none * @return none *************************************************************************/ static void publishTreatmentRecircData( void ) { if ( ++recircPublishTimerCtr >= RECIRC_DATA_PUBLISH_INTERVAL ) { TREATMENT_RECIRC_PAYLOAD_T data; U32 ticksPerSec = ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ); U32 elapsedSecs = ( recircTimerCtr / ticksPerSec ); U32 timeoutSecs = ( recircTimeoutTicks / ticksPerSec ); data.countdown = ( recircTimerCtr < recircTimeoutTicks ) ? ( timeoutSecs - elapsedSecs ) : 0U; recircPublishTimerCtr = 0; broadcastData( MSG_ID_TD_RECIRCULATE_DATA, COMM_BUFFER_OUT_CAN_TD_BROADCAST, (U08*)&data, sizeof( TREATMENT_RECIRC_PAYLOAD_T ) ); } } /**@}*/