/* * TreatmentEnd.c * * Created on: Aug 5, 2026 * Author: fw */ #include "AirTrap.h" #include "BloodFlow.h" #include "ModeTreatment.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" /** * @addtogroup TreatmentEnd * @{ */ // ********** private definitions *********** /// Placeholder End of Treatment blood pump flow rate (mL/min). // TODO CONFIRM #6: reuse TREATMENT_PARAM_BLOOD_FLOW via getTreatmentParameterU32(), or a // dedicated End-of-Treatment rate param — not yet located in this codebase. Using a local // placeholder constant here in the meantime so the file compiles. #define TX_END_BP_FLOW_RATE_ML_MIN 150 /// Interval at which treatment end progress is to be published to UI. #define TREATMENT_END_DATA_PUBLISH_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) /// 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 ); typedef enum ENDTREATMENT_STATES_T { TX_END_WAIT_FOR_RINSEBACK_STATE, TX_END_FLUID_BOLUS_STATE, TX_END_PAUSED_STATE, NO_OF_TX_END_STATES } ENDTREATMENT_STATES_T; // ********** private data ********** static ENDTREATMENT_STATES_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 treatmentEndPublishTimerCtr; ///< Timer counter for UI publish interval. static BOOL txEndAlarmRinsebackRequested; ///< User requested rinseback via alarm action / UI message. static BOOL txEndAlarmEndTreatmentOnlyRequested; ///< User requested "End Treatment Only" 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. // ********** private function prototypes ********** static void setupForTxEndWait4RinsebackState( void ); static void setupForTxEndPausedState( void ); static void resetTreatmentEndFlags( void ); static void publishTreatmentEndData( void ); static ENDTREATMENT_STATES_T handleTxEndWait4RinsebackState( void ); static ENDTREATMENT_STATES_T handleTxEndFluidBolusState( void ); static ENDTREATMENT_STATES_T handleTxEndPausedState( void ); /*********************************************************************//** * @brief * The initTreatmentEnd function initializes the Treatment End sub-mode. * @details \b Inputs: none * @details \b Outputs: treatmentEndState, txEndTimerCtr, treatmentEndPublishTimerCtr, * txEndEscalated, txEndAlarmRinsebackRequested, txEndAlarmEndTreatmentOnlyRequested, * txEndBolusRequested * @return none *************************************************************************/ void initTreatmentEnd( void ) { treatmentEndState = TX_END_WAIT_FOR_RINSEBACK_STATE; txEndTimerCtr = 0; treatmentEndPublishTimerCtr = 0; txEndEscalated = FALSE; resetTreatmentEndFlags(); } /*********************************************************************//** * @brief * The transitionToTreatmentEnd function prepares for transition to treatment * end sub-mode: stops heparin delivery, bypasses the dialyzer, branches DD * dialysate generation based on priming fluid type, sets up Wait for Rinseback * actuator states, and triggers the low-priority End of Treatment alarm. * @details \b Inputs: treatmentEndState * @details \b Outputs: treatmentEndState (via initTreatmentEnd), DD dialysate * delivery command set (via cmdBypassDialyzer/cmdStopGenerateDialysate/ * cmdChangeQd/cmdSubstitutionRate — owned by DDInterface.c, not local to this * module) * @return none *************************************************************************/ void transitionToTreatmentEnd( void ) { initTreatmentEnd(); // Dialyzer bypass — confirmed real call (DDInterface.c). cmdBypassDialyzer( TRUE ); // Heparin stop — confirmed real call. stopSyringePump(); // DD saline / substitution branch. // Confirmed real DD calls exist (DDInterface.c): cmdStopGenerateDialysate() for saline, // cmdChangeQd() to set Qdt=Qb for substitution branch (generation already running/bypassed // from treatment start, no separate "mode" command needed). // TODO CONFIRM: real flag/param that indicates priming fluid was saline vs substitution // (per manager: check bolus flags + substitution rate — exact names not yet located). // TODO CONFIRM (PRS-429 conflict): requirement text says Qdt = institutionally configured // Rinseback Flow Rate for the substitution branch, but the PRS's own rationale note says // Qdt = Qb. Code below uses Qb pending resolution — verify before finalizing. if ( TRUE == /* TODO: real saline-fluid check */ FALSE ) { cmdStopGenerateDialysate(); } else { // Qdt = Qb — confirmed real call pattern (StatePreTxRecirculate.c uses // cmdChangeQd(getTreatmentParameterU32(TREATMENT_PARAM_...))). Blood flow rate // source confirmed as TREATMENT_PARAM_BLOOD_FLOW per TxParams.h accessor pattern. cmdChangeQd( (F32)getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ) ); } // Substitution flow Off — confirmed real call (DDInterface.c). Modality scope (all vs // HDF-only) still needs confirmation before this is unconditional. // TODO CONFIRM #10: modality scope check before calling. cmdSubstitutionRate( 0.0F ); setCurrentSubState( (U32)treatmentEndState ); setupForTxEndWait4RinsebackState(); if ( FLUID_TYPE_SALINE == getFluidType() ) { cmdStopGenerateDialysate(); // saline branch — DD stops } else { cmdChangeQd( (F32)getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ) ); // substitution branch — DD keeps running, Qdt=Qb } // Venous bubble detection stays enabled always — confirmed real call, same pattern // used in ModeTreatment.c's transitionToTreatmentMode(): // setVenousBubbleDetectionEnabled( H18_BBLD, TRUE ); // Trigger the low-priority End of Treatment alarm — this IS the notification (no UI dialog). activateAlarmNoData( ALARM_ID_TD_END_OF_TREATMENT ); } /*********************************************************************//** * @brief * Sets actuators for Wait for Rinseback substate entry: requires the door * closed, opens the arterial and venous pinch valves, commands the blood * pump to the End of Treatment flow rate, and continues air trap level control. * @details \b Inputs: none * @details \b Outputs: door-closed-required state, H1_VALV/H19_VALV position, * blood pump target rate, air trap controller start request * @return none *************************************************************************/ static void setupForTxEndWait4RinsebackState( void ) { // Confirmed real calls/signatures from StatePreTxRecirculate.c. doorClosedRequired( TRUE ); setValvePosition( H1_VALV, VALVE_POSITION_B_OPEN ); setValvePosition( H19_VALV, VALVE_POSITION_B_OPEN ); // TODO CONFIRM #6: TX_END_BP_FLOW_RATE_ML_MIN is a placeholder value (150) — confirm // whether End of Treatment reuses TREATMENT_PARAM_BLOOD_FLOW directly (no rate change) // or commands a dedicated slower End-of-Treatment rate, per PRS-810 ("keep blood flowhb // rate at the set rate"). If PRS-810 is authoritative, replace with: // getTreatmentParameterU32( TREATMENT_PARAM_BLOOD_FLOW ) setBloodPumpTargetFlowRate( TX_END_BP_FLOW_RATE_ML_MIN, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_CLOSED_LOOP ); // Continue air trap level control — confirmed real call (AirTrap.c). Air trap // controller runs continuously once started; this just (re)requests control if // not already controlling. No stop/start toggling needed beyond entry/pause. startAirTrapControl(); } /*********************************************************************//** * @brief * Sets actuators for Paused substate entry (alarm requires pump stop): * stops the blood pump and stops air trap level control. * @details \b Inputs: none * @details \b Outputs: blood pump state (via signalBloodPumpHardStop), air trap * controller stop request * @return none *************************************************************************/ static void setupForTxEndPausedState( void ) { // Confirmed real call (StatePreTxRecirculate.c: handlePreTxRecirculateState() calls // this identically when doesAlarmStatusIndicateStop() is TRUE). signalBloodPumpHardStop(); // Stop air trap level control — confirmed real call (AirTrap.c). Note: // endAirTrapControl() also internally calls signalLowVenousPressureCheck() to // keep venous pressure checking active even after auto air trap control ends. endAirTrapControl(); // Confirmed: doorClosedRequired() stays TRUE for every Treatment End substate — // no release call needed here. Set once at Wait for Rinseback entry and left as-is. } /*********************************************************************//** * @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. * @details \b Inputs: treatmentEndState, txEndTimerCtr, txEndEscalated, * txEndAlarmRinsebackRequested, txEndAlarmEndTreatmentOnlyRequested, * txEndBolusRequested * @details \b Outputs: treatmentEndState, txEndTimerCtr, txEndEscalated, * treatmentEndPublishTimerCtr, (flags reset via resetTreatmentEndFlags) * @return none *************************************************************************/ void execTreatmentEnd( void ) { ENDTREATMENT_STATES_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 TX_END_WAIT_FOR_RINSEBACK_STATE: treatmentEndState = handleTxEndWait4RinsebackState(); break; case TX_END_FLUID_BOLUS_STATE: treatmentEndState = handleTxEndFluidBolusState(); break; case TX_END_PAUSED_STATE: treatmentEndState = handleTxEndPausedState(); break; default: // Confirmed real macro/pattern (StatePreTxRecirculate.c: identical structure // with SW_FAULT_ID_PRE_TX_RECIRC_INVALID_STATE). Fault ID name below follows // the same naming convention but is not itself a confirmed enum value — // confirm SW_FAULT_ID_TREATMENT_END_INVALID_STATE exists before building. SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_TREATMENT_END_INVALID_STATE, (U32)treatmentEndState ); break; } if ( priorSubState != treatmentEndState ) { // Confirmed real pattern (StatePreTxRecirculate.c: execPreTxRecirculate()). setCurrentSubState( (U32)treatmentEndState ); SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_SUB_STATE_CHANGE, priorSubState, treatmentEndState ); } publishTreatmentEndData(); // Flags should be handled by now — reset in case not handled by current state. resetTreatmentEndFlags(); } /*********************************************************************//** * @brief * Wait for Rinseback substate — all three user options available immediately, * no escalation-gating. Also handles transition to Paused if an alarm * requires blood pump stop. * @details \b Inputs: txEndAlarmRinsebackRequested, txEndAlarmEndTreatmentOnlyRequested, * txEndBolusRequested * @details \b Outputs: none (transitions reported via return value) * @return next Treatment End substate *************************************************************************/ static ENDTREATMENT_STATES_T handleTxEndWait4RinsebackState( void ) { ENDTREATMENT_STATES_T result = TX_END_WAIT_FOR_RINSEBACK_STATE; // Confirmed real call (StatePreTxRecirculate.c: handlePreTxRecirculateState()). if ( TRUE == doesAlarmStatusIndicateStop() ) { setupForTxEndPausedState(); result = TX_END_PAUSED_STATE; } else if ( TRUE == txEndAlarmRinsebackRequested ) { // Set via signalTreatmentEndRinsebackRequest(), called by the UI message handler // when the Rinseback / "Return Blood & End Treatment" option is selected. // PENDING: you're checking whether an existing enable/gating flag controls this // path before wiring the real call — hold here until confirmed. Once resolved, // this branch calls the real rinseback transition function (name still TBD). } else if ( TRUE == txEndAlarmEndTreatmentOnlyRequested ) { // Set via signalTreatmentEndOnlyRequest(), called by the UI message handler // when "End Treatment Only" is selected. // PENDING: confirm exact call with whoever implemented Post-Treatment mode — // per your note, this returns to the original/prior state, consistent with the // MODE_POST pattern already seen elsewhere in ModeTreatment.c // (e.g. requestNewOperationMode(MODE_POST)). Wire once confirmed. } else if ( TRUE == txEndBolusRequested ) { result = TX_END_FLUID_BOLUS_STATE; // TODO: confirm real fluid bolus initiate call. } return result; } /*********************************************************************//** * @brief * Fluid Bolus substate — delegates to existing fluid bolus service. * @details \b Inputs: none * @details \b Outputs: none (transitions reported via return value) * @return next Treatment End substate *************************************************************************/ static ENDTREATMENT_STATES_T handleTxEndFluidBolusState( void ) { ENDTREATMENT_STATES_T result = TX_END_FLUID_BOLUS_STATE; // Confirmed: execFluidBolus() is already called unconditionally every cycle at the // mode level (ModeTreatment.c's execTreatmentMode()), regardless of Treatment End // substate — no local call needed here. This substate only needs to poll completion. // TODO: confirm real fluid bolus completion-check call for this codebase. return result; } /*********************************************************************//** * @brief * Paused substate — entered only if an alarm requires blood pump stop. * Offers Rinseback and End Treatment Only directly, same as Wait for Rinseback. * @details \b Inputs: txEndAlarmRinsebackRequested, txEndAlarmEndTreatmentOnlyRequested * @details \b Outputs: none (transitions reported via return value) * @return next Treatment End substate *************************************************************************/ static ENDTREATMENT_STATES_T handleTxEndPausedState( void ) { ENDTREATMENT_STATES_T result = TX_END_PAUSED_STATE; if ( TRUE == txEndAlarmRinsebackRequested ) { // PENDING: same rinseback call as Wait for Rinseback — pending flag check. } else if ( TRUE == txEndAlarmEndTreatmentOnlyRequested ) { // PENDING: same operation-mode-change call as Wait for Rinseback. } else if ( FALSE == doesAlarmStatusIndicateStop() ) { // Confirmed real call, same pattern used to detect the stop condition on entry. setupForTxEndWait4RinsebackState(); result = TX_END_WAIT_FOR_RINSEBACK_STATE; } return result; } /*********************************************************************//** * @brief * Resets all Treatment End request flags. * @details \b Inputs: none * @details \b Outputs: txEndAlarmRinsebackRequested, txEndAlarmEndTreatmentOnlyRequested, * txEndBolusRequested * @return none *************************************************************************/ static void resetTreatmentEndFlags( void ) { txEndAlarmRinsebackRequested = FALSE; txEndAlarmEndTreatmentOnlyRequested = FALSE; txEndBolusRequested = FALSE; } /*********************************************************************//** * @brief * Publishes Treatment End progress to UI at set interval. * @details \b Inputs: treatmentEndPublishTimerCtr * @details \b Outputs: treatmentEndPublishTimerCtr * @return none *************************************************************************/ static void publishTreatmentEndData( void ) { if ( ++treatmentEndPublishTimerCtr >= TREATMENT_END_DATA_PUBLISH_INTERVAL ) { treatmentEndPublishTimerCtr = 0; // TODO: confirm payload contents/message ID for Treatment End status broadcast to UI. // You're updating this yourself once decided. } } /*********************************************************************//** * @brief * Signal functions called externally (e.g. from message handlers, or from * ModeTreatment.c's handleTreatmentEndState() translating alarm action flags) * when the corresponding user action / confirmation is received. Rinseback * and End Treatment Only are both expected to arrive as UI messages via the * firmware message handler, not as locally-detected conditions. * @details \b Inputs: none * @details \b Outputs: txEndAlarmRinsebackRequested / txEndAlarmEndTreatmentOnlyRequested / * txEndBolusRequested (one each, respectively) * @return none *************************************************************************/ void signalTreatmentEndRinsebackRequest( void ) { txEndAlarmRinsebackRequested = TRUE; } void signalTreatmentEndOnlyRequest( void ) { txEndAlarmEndTreatmentOnlyRequested = TRUE; } void signalTreatmentEndFluidBolusRequest( void ) { txEndBolusRequested = TRUE; } /*********************************************************************//** * @brief * The getCurrentTreatmentEndState function returns the current substate of * the Treatment End sub-mode. Referenced by TreatmentEnd.h and expected by * ModeTreatment.c's broadcastTreatmentTimeAndState() (payload.txEndState). * @details \b Inputs: treatmentEndState * @details \b Outputs: none * @return current Treatment End substate *************************************************************************/ U32 getCurrentTreatmentEndState( void ) { return (U32)treatmentEndState; } /**@}*/