Index: firmware/App/Modes/ModeTreatment.c =================================================================== diff -u -r83310420ae862de4724f8cfbbaeda9936c47801b -r9e2e190687fb9809a3890ae6f1cee0d860c3db46 --- firmware/App/Modes/ModeTreatment.c (.../ModeTreatment.c) (revision 83310420ae862de4724f8cfbbaeda9936c47801b) +++ firmware/App/Modes/ModeTreatment.c (.../ModeTreatment.c) (revision 9e2e190687fb9809a3890ae6f1cee0d860c3db46) @@ -636,7 +636,8 @@ // sendLastTreatmentPeriodicData = TRUE; // treatmentEndTimeStamp = getRTCTimestamp(); pauseDialysis(); - requestNewOperationMode( MODE_STAN ); // TODO transitionToTreatmentEnd(); + //requestNewOperationMode( MODE_STAN ); // TODO + transitionToTreatmentEnd(); //SET_ALARM_WITH_1_U32_DATA( ALARM_ID_TD_END_OF_TREATMENT_WARNING, presTreatmentTimeSecs ); result = TREATMENT_END_STATE; } Index: firmware/App/Modes/TreatmentEnd.c =================================================================== diff -u --- firmware/App/Modes/TreatmentEnd.c (revision 0) +++ firmware/App/Modes/TreatmentEnd.c (revision 9e2e190687fb9809a3890ae6f1cee0d860c3db46) @@ -0,0 +1,442 @@ +/* + * 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; +} + +/**@}*/ Index: firmware/App/Modes/TreatmentEnd.h =================================================================== diff -u --- firmware/App/Modes/TreatmentEnd.h (revision 0) +++ firmware/App/Modes/TreatmentEnd.h (revision 9e2e190687fb9809a3890ae6f1cee0d860c3db46) @@ -0,0 +1,46 @@ +/************************************************************************** +* +* Copyright (c) 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 TreatmentEnd.h +* +* @author (last) +* @date (last) +* +* @author (original) +* @date (original) 05-Aug-2026 +* +***************************************************************************/ +#ifndef __TREATMENT_END_H__ +#define __TREATMENT_END_H__ + +#include "TDCommon.h" + +/** + * @defgroup TreatmentEnd TreatmentEnd + * @brief The Treatment End unit manages the End of Treatment sub-mode, + * including waiting for user-initiated rinseback, in-state fluid bolus + * delivery, and alarm-driven pause handling. + * + * @addtogroup TreatmentEnd + * @{ + */ + +// ********** public definitions ********** + +// ********** public function prototypes ********** + +void initTreatmentEnd( void ); +void transitionToTreatmentEnd( void ); +void execTreatmentEnd( void ); +U32 getCurrentTreatmentEndState( void ); +void signalTreatmentEndRinsebackRequest( void ); +void signalTreatmentEndOnlyRequest( void ); +void signalTreatmentEndFluidBolusRequest( void ); + +/**@}*/ + +#endif Index: firmware/App/Services/AlarmMgmtSWFaults.h =================================================================== diff -u -r8345f0415ce34f2f47afcbf613a00216769f51c6 -r9e2e190687fb9809a3890ae6f1cee0d860c3db46 --- firmware/App/Services/AlarmMgmtSWFaults.h (.../AlarmMgmtSWFaults.h) (revision 8345f0415ce34f2f47afcbf613a00216769f51c6) +++ firmware/App/Services/AlarmMgmtSWFaults.h (.../AlarmMgmtSWFaults.h) (revision 9e2e190687fb9809a3890ae6f1cee0d860c3db46) @@ -199,6 +199,7 @@ SW_FAULT_ID_PRE_TX_RECIRC_INVALID_STATE = 168, SW_FAULT_ID_TD_VALVES_INVALID_VALVE2 = 169, SW_FAULT_ID_INVALID_TUBE_SET_TYPE = 170, + SW_FAULT_ID_TREATMENT_END_INVALID_STATE=171, NUM_OF_SW_FAULT_IDS } SW_FAULT_ID_T;