/* * TreatmentEnd.c * * Created on: Aug 5, 2026 * Author: fw */ #include "DDInterface.h" #include "string.h" #include "AirTrap.h" #include "BloodFlow.h" #include "ModeTreatment.h" #include "ModeStandby.h" #include "OperationModes.h" #include "SyringePump.h" #include "TaskGeneral.h" #include "TreatmentEnd.h" #include "Valves.h" #include "DDInterface.h" #include "FluidBolus.h" #include "AlarmMgmt.h" #include "TxParams.h" #include "Bubbles.h" #include "Switches.h" #include "Common.h" #include "Messaging.h" #include "TDDefs.h" #include "StateTxPaused.h" /** * @addtogroup TreatmentEnd * @{ */ // ********** private definitions *********** /// Time before escalating the low-priority End of Treatment alarm to high priority. static const U32 TX_END_ESCALATION_TIMEOUT_MS = ( ( 10 * 60 * MS_PER_SECOND ) / TASK_GENERAL_INTERVAL ); // ********** private data ********** static TREATMENT_END_STATE_T treatmentEndState; ///< Current substate of Treatment End. static U32 txEndTimerCtr; ///< Timer counter (GP task intervals) tracking time in Wait for Rinseback, drives low->high alarm escalation. static U32 bloodSittingTimerCtr; ///< blood sitting timer counter static BOOL txEndAlarmRinsebackRequested; ///< User requested rinseback via alarm action / UI message. static BOOL txEndBolusRequested; ///< User requested a fluid bolus while in End of Treatment. static BOOL txEndEscalated; ///< Tracks whether the high-priority alarm has already been raised, to avoid re-triggering every cycle. static TREATMENT_END_STATE_T txEndPriorStateBeforeBolus; // ********** private function prototypes ********** static TREATMENT_END_STATE_T handleTxEndWaitForRinsebackState( void ); static TREATMENT_END_STATE_T handleTxEndFluidBolusState( void ); static TREATMENT_END_STATE_T handleTxEndPausedState( void ); static void resetTreatmentEndFlags( void ); static void setupForTreatmentEndSubState( TREATMENT_END_STATE_T newState ); static void handleTreatmentEndBloodSittingTimer( void ); static BOOL handleTxEndRinsebackUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ); /*********************************************************************//** * @brief * The initTreatmentEnd function initializes the Treatment End sub-mode. * @details \b Inputs: none * @details \b Outputs: treatmentEndState, txEndTimerCtr, txEndEscalated, * bloodSittingTimerCtr * @return none *************************************************************************/ void initTreatmentEnd( void ) { treatmentEndState = TREATMENT_END_WAIT_FOR_RINSEBACK_STATE; txEndTimerCtr = 0; txEndEscalated = FALSE; bloodSittingTimerCtr = 0; resetTreatmentEndFlags(); } /*********************************************************************//** * @brief * The transitionToTreatmentEnd function prepares for transition to * Treatment End sub-mode. * @details \b Inputs: treatmentEndState * @details \b Outputs: none * @return none *************************************************************************/ void transitionToTreatmentEnd( void ) { initTreatmentEnd(); cmdBypassDialyzer( TRUE ); stopSyringePump(); setAlarmUserActionEnabled( ALARM_USER_ACTION_RESUME, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_RINSEBACK, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_END_TREATMENT, TRUE ); if ( FLUID_TYPE_SALINE == getFluidType() ) { cmdStopGenerateDialysate(); } else { ///CKOE by passing the value manually suppose give 100 for substitution and check once //CKOE write a print for get function cmdSubstitutionRate((F32) getSysConfigTreatmentParameterU32DefaultValue( TREATMENT_PARAM_RINSEBACK_FLOW_RATE )); } setCurrentSubState( (U32)treatmentEndState ); setupForTreatmentEndSubState(TREATMENT_END_WAIT_FOR_RINSEBACK_STATE); setVenousBubbleDetectionEnabled( H18_BBLD, TRUE ); // // Trigger the low-priority End of Treatment alarm // activateAlarmNoData( ALARM_ID_TD_END_OF_TREATMENT ); } /*********************************************************************//** * @brief * Tracks how long blood has been sitting (not recirculating) while in * Treatment End Paused substate, raising warning/max-timeout alarms as * thresholds are exceeded. * @details \b Alarm: ALARM_ID_TD_BLOOD_SITTING_WARNING / * ALARM_ID_TD_BLOOD_SITTING_TOO_LONG as thresholds are exceeded. * @details \b Inputs: bloodSittingTimerCtr * @details \b Outputs: bloodSittingTimerCtr * @return none *************************************************************************/ static void handleTreatmentEndBloodSittingTimer( void ) { // Ensure we do not sit in stopped state for too long (if blood in line) bloodSittingTimerCtr++; if ( FALSE == doesAlarmStatusIndicateEndTxOnly() ) // Alarms appropriate only if we are not already at an alarm stop, end Tx only { if ( bloodSittingTimerCtr > WARN_TIME_BLOOD_SITTING ) { activateAlarmNoData( ALARM_ID_TD_BLOOD_SITTING_WARNING ); } if ( bloodSittingTimerCtr > MAX_TIME_BLOOD_SITTING ) { // Activate the alarm activateAlarmNoData( ALARM_ID_TD_BLOOD_SITTING_TOO_LONG ); } } } /*********************************************************************//** * @brief * Applies actuator/config setup for the Treatment End substate being entered. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if newState is invalid. * @details \b Inputs: none * @details \b Outputs: bloodSittingTimerCtr * @param newState substate being entered * @return none *************************************************************************/ static void setupForTreatmentEndSubState( TREATMENT_END_STATE_T newState ) { switch ( newState ) { case TREATMENT_END_WAIT_FOR_RINSEBACK_STATE: doorClosedRequired( TRUE ); 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_OPEN_LOOP ); startAirTrapControl(); break; case TREATMENT_END_PAUSED_STATE: bloodSittingTimerCtr = 0; signalBloodPumpHardStop(); endAirTrapControl(); setValvePosition( H1_VALV, VALVE_POSITION_C_CLOSE ); setValvePosition( H19_VALV, VALVE_POSITION_C_CLOSE ); break; case TREATMENT_END_FLUID_BOLUS_STATE: doorClosedRequired( TRUE ); 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_OPEN_LOOP ); startAirTrapControl(); signalStartFluidBolus( getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ) ); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_TREATMENT_END_INVALID_STATE, (U32)newState ); break; } } /*********************************************************************//** * @brief * The execTreatmentEnd function executes the Treatment End sub-mode * state machine. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if the current Treatment End * substate is invalid; ALARM_ID_TD_END_OF_TREATMENT_TIMEOUT on escalation timeout. * @details \b Inputs: treatmentEndState, txEndTimerCtr, txEndEscalated * @details \b Outputs: treatmentEndState, txEndTimerCtr, txEndEscalated * @return none *************************************************************************/ void execTreatmentEnd( void ) { TREATMENT_END_STATE_T priorSubState = treatmentEndState; // Escalate low -> high alarm after confirmed 10-minute timeout, once only. if ( ( ++txEndTimerCtr > TX_END_ESCALATION_TIMEOUT_MS ) && ( FALSE == txEndEscalated ) ) { activateAlarmNoData( ALARM_ID_TD_END_OF_TREATMENT_TIMEOUT ); txEndEscalated = TRUE; } switch ( treatmentEndState ) { case TREATMENT_END_WAIT_FOR_RINSEBACK_STATE: treatmentEndState = handleTxEndWaitForRinsebackState(); break; case TREATMENT_END_FLUID_BOLUS_STATE: treatmentEndState = handleTxEndFluidBolusState(); break; case TREATMENT_END_PAUSED_STATE: handleTreatmentEndBloodSittingTimer(); treatmentEndState = handleTxEndPausedState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_TREATMENT_END_INVALID_STATE, (U32)treatmentEndState ); break; } if ( priorSubState != treatmentEndState ) { setupForTreatmentEndSubState( treatmentEndState ); setCurrentSubState( (U32)treatmentEndState ); SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_SUB_STATE_CHANGE, priorSubState, treatmentEndState ); } // Flags should be handled by now — reset in case not handled by current state. resetTreatmentEndFlags(); } /*********************************************************************//** * @brief * Wait for Rinseback substate — rinseback and fluid bolus requests are * available immediately, no escalation-gating. Also handles transition * to Paused if an alarm requires blood pump stop. * @details \b Inputs: txEndAlarmRinsebackRequested, txEndBolusRequested * @details \b Outputs: none * @return next Treatment End substate *************************************************************************/ static TREATMENT_END_STATE_T handleTxEndWaitForRinsebackState( void ) { TREATMENT_END_STATE_T result = TREATMENT_END_WAIT_FOR_RINSEBACK_STATE; if ( TRUE == doesAlarmStatusIndicateStop() ) { result = TREATMENT_END_PAUSED_STATE; } else if ( TRUE == txEndAlarmRinsebackRequested ) { signalTreatmentEndToRinseback(); } else if ( TRUE == txEndBolusRequested ) { txEndPriorStateBeforeBolus = TREATMENT_END_WAIT_FOR_RINSEBACK_STATE; result = TREATMENT_END_FLUID_BOLUS_STATE; } return result; } /*********************************************************************//** * @brief * Fluid Bolus substate — delegates to existing fluid bolus service. * @details \b Inputs: none * @details \b Outputs: none * @return next Treatment End substate *************************************************************************/ static TREATMENT_END_STATE_T handleTxEndFluidBolusState( void ) { TREATMENT_END_STATE_T result = TREATMENT_END_FLUID_BOLUS_STATE; if ( FALSE == isFluidBolusActive() ) { if ( TRUE == doesAlarmStatusIndicateStop() ) { result= TREATMENT_END_PAUSED_STATE; } else { result= TREATMENT_END_WAIT_FOR_RINSEBACK_STATE; } } return result; } /*********************************************************************//** * @brief * Paused substate — entered only if an alarm requires blood pump stop. * @details \b Inputs: none * @details \b Outputs: none * @return next Treatment End substate *************************************************************************/ static TREATMENT_END_STATE_T handleTxEndPausedState( void ) { TREATMENT_END_STATE_T result = TREATMENT_END_PAUSED_STATE; if ( FALSE == isBloodRecircBlocked() ) { result = TREATMENT_END_WAIT_FOR_RINSEBACK_STATE; } // else if ( TRUE == txEndAlarmRinsebackRequested ) // { // signalTreatmentEndToRinseback(); // } // // else if ( TRUE == txEndBolusRequested ) // { // bloodSittingTimerCtr = 0; // txEndPriorStateBeforeBolus = TREATMENT_END_PAUSED_STATE; // result = TREATMENT_END_FLUID_BOLUS_STATE; // } return result; } /*********************************************************************//** * @brief * Resets all Treatment End request flags. * @details \b Inputs: none * @details \b Outputs: txEndAlarmRinsebackRequested, txEndBolusRequested * @return none *************************************************************************/ static void resetTreatmentEndFlags( void ) { txEndAlarmRinsebackRequested = FALSE; txEndBolusRequested = FALSE; } /*********************************************************************//** * @brief * The signalTreatmentEndFluidBolusRequest function signals a user request * for a fluid bolus while in Treatment End. * @details \b Inputs: treatmentEndState * @details \b Outputs: txEndBolusRequested * @return TRUE if request accepted, FALSE if rejected *************************************************************************/ BOOL signalTreatmentEndFluidBolusRequest( void ) { BOOL result = FALSE; // TODO REMOVE PAUSED STATE IF W CANT ABLE TO MOVE TO RINSEBACK UPON ANY ALARMS if ( ( TREATMENT_END_WAIT_FOR_RINSEBACK_STATE == treatmentEndState ) || ( TREATMENT_END_PAUSED_STATE == treatmentEndState ) ) { txEndBolusRequested = TRUE; result = TRUE; } return result; } /*********************************************************************//** * @brief * The getCurrentTreatmentEndState function returns the current substate of * the Treatment End sub-mode. * @details \b Inputs: treatmentEndState * @details \b Outputs: none * @return current Treatment End substate *************************************************************************/ U32 getCurrentTreatmentEndState( void ) { return (U32)treatmentEndState; } /*********************************************************************//** * @brief * The signalTreatmentEndAlarmRinsebackUserAction function is called by * ModeTreatment.c when the user has requested rinseback via an alarm * recovery action while in Treatment End. * @details \b Inputs: none * @details \b Outputs: txEndAlarmRinsebackRequested * @return none *************************************************************************/ void signalTreatmentEndAlarmRinsebackUserAction( void ) { txEndAlarmRinsebackRequested = TRUE; } /*********************************************************************//** * @brief * The handleTxEndRinsebackUserAction function handles an end treatment * rinseback user action request. * @details \b Inputs: treatmentEndState * @details \b Outputs: txEndAlarmRinsebackRequested * @param rejReason Code indicating reason for rejection * @return TRUE if user action accepted, FALSE if not *************************************************************************/ static BOOL handleTxEndRinsebackUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ) { BOOL result = FALSE; if ( TREATMENT_END_WAIT_FOR_RINSEBACK_STATE == treatmentEndState ) { result = TRUE; txEndAlarmRinsebackRequested = TRUE; } else { *rejReason = REQUEST_REJECT_REASON_ACTION_DISABLED_IN_CURRENT_STATE; } return result; } /*********************************************************************//** * @brief * The handleTreatmentEndUserAction function handles a UI Treatment End * command request message. * @details \b Inputs: none * @details \b Outputs: none * @param message Incoming UI message containing the requested action. * @return TRUE if request is accepted, FALSE if rejected. *************************************************************************/ BOOL handleTreatmentEndUserAction( 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) ); // Reject if any alarm active — user must clear alarm first. if ( FALSE == isAnyAlarmActive() ) { switch ( (REQUESTED_TREATMENT_END_USER_ACTIONS_T)cmd ) { case REQUESTED_USER_ACTION_TX_END_RINSEBACK_START: result = handleTxEndRinsebackUserAction( &rejReason ); 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; } response.accepted = result; response.rejectionReason = rejReason; sendMessage( MSG_ID_TD_END_TREATMENT_RESPONSE, COMM_BUFFER_OUT_CAN_TD_2_UI, (U08*)&response, sizeof( UI_RESPONSE_PAYLOAD_T ) ); return result; } /**@}*/