/************************************************************************** * * Copyright (c) 2024-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 ModeHeatDisinfect.c * * @author (last) Jashwant Gantyada * @date (last) 18-Aug-2026 * * @author (original) Jashwant Gantyada * @date (original) 17-Aug-2026 * ***************************************************************************/ #include "BalancingChamber.h" #include "ConcentratePumps.h" #include "DialysatePumps.h" #include "Heaters.h" #include "Level.h" #include "MessagePayloads.h" #include "MessageSupport.h" #include "Messaging.h" #include "ModeFault.h" #include "ModeHeatDisinfect.h" #include "OperationModes.h" #include "Pressure.h" #include "RinsePump.h" #include "SubstitutionPump.h" #include "TaskGeneral.h" #include "Temperature.h" #include "Timers.h" #include "Valves.h" /** * @addtogroup DDHeatDisinfectMode * @{ */ // ********** private definitions ********** #define HEAT_DISINFECT_DATA_PUBLISH_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Interval at which heat disinfect mode data is published. #define HEAT_DISINFECT_DWELL_TIME_MS ( 10 * 60 * MS_PER_SECOND ) ///< Required dwell time after the sensor is above target. #define HEAT_DISINFECT_D78_DWELL_TEMP_C 80.0F ///< DD recirc dwell starts when D78 exceeds this (deg C). #define HEAT_DISINFECT_D4_DWELL_TEMP_C 80.0F ///< WCID dwell counts while D4 exceeds this (deg C). #define HEAT_DISINFECT_HEATER_TARGET_C 90.0F ///< D5 heater target temperature during DD recirc (deg C). #define HEAT_DISINFECT_HYDROBLOCK_TARGET_C 88.0F ///< D5 heater target temperature during hydroblock recirc (deg C). #define HEAT_DISINFECT_HYDROBLOCK_STOP_TEMP_C 88.0F ///< Stop D5 when D4 reaches this temperature (deg C). #define HEAT_DISINFECT_D18_HEATER_ENABLE_PSI 3.0F ///< Minimum D18 pressure before D5 is started (PSI). #define HEAT_DISINFECT_D66_TARGET_PSI 3.0F ///< D65 pressure target (PSI). #define HEAT_DISINFECT_SETUP_STABILITY_MS ( 2 * 60 * MS_PER_SECOND ) ///< DD recirc setup stability before open loop (ms). #define HEAT_DISINFECT_DEPRES_D80_DELAY_MS 100 ///< Delay after D64 open before opening D80 (ms). #define HEAT_DISINFECT_DEPRES_D65_OPEN_MS ( 1 * MS_PER_SECOND ) ///< D65 open duration after D80 opens, then close D65 and exit (ms). #define HEAT_DISINFECT_LINE_RECIRC_MS ( 5 * MS_PER_SECOND ) ///< DD line recirc cadence (ms). #define HEAT_DISINFECT_D76_D47_RECIRC_MS ( 10 * MS_PER_SECOND ) ///< D76/D47 line recirc duration (ms). #define HEAT_DISINFECT_BICART_SUPPLY_MS ( 6 * MS_PER_SECOND ) ///< WCID bicart supply duration (ms). #define HEAT_DISINFECT_BICART_VENT_MS ( 6 * MS_PER_SECOND ) ///< WCID bicart vent duration (ms). #define HEAT_DISINFECT_WCID_DWELL_CHECK_MS ( 6 * MS_PER_SECOND ) ///< Mix time before WCID D4 dwell check (ms). #define HEAT_DISINFECT_D12_RECIRC_RPM 930 ///< Open-loop D12 speed for DD recirculation. #define HEAT_DISINFECT_D48_RECIRC_RPM 2240 ///< Open-loop D48 speed for DD recirculation. #define HEAT_DISINFECT_D12_HYDROBLOCK_RPM 1000 ///< D12 speed during hydroblock heating. #define HEAT_DISINFECT_D12_POST_RECIRC_RPM 700 ///< D12 speed during WCID bicart recirculation. #define HEAT_DISINFECT_D12_DWELL_CHECK_RPM 1000 ///< D12 mix speed before WCID dwell check. #define HEAT_DISINFECT_CONC_FLUSH_SPEED_ML_MIN 50.0F ///< D10/D11/D76 recirculation speed (mL/min). #define HEAT_DISINFECT_HDF_PUMP_SPEED_ML_MIN 50.0F ///< D92 recirculation speed (mL/min). /// Bicart depressurize sub-states. D64 and D80 remain open on exit. typedef enum Heat_Depressurize_Sub_States { HEAT_DEPRES_WAIT_D6 = 0, ///< Wait until D6 is full HEAT_DEPRES_OPEN_D64, ///< Open D64; D80 opens after HEAT_DISINFECT_DEPRES_D80_DELAY_MS HEAT_DEPRES_OPEN_D80, ///< Open D80 and D65, then close D65 after HEAT_DISINFECT_DEPRES_D65_OPEN_MS NUM_OF_HEAT_DEPRES_SUB_STATES ///< Number of depressurize sub-states } HEAT_DEPRES_SUB_STATE_T; // ********** private data ********** static DD_HEAT_DISINFECT_STATE_T heatDisinfectState; ///< Currently active heat disinfect state. static HEAT_DEPRES_SUB_STATE_T depressurizeSubState; ///< Bicart depressurize sub-state. static U32 stateTimerMS; ///< State or sub-state entry timestamp (ms). static U32 ddDwellAccumulatedMS; ///< Banked DD dwell time while D78 was above target (ms). static BOOL ddDwellAtTarget; ///< TRUE while D78 is currently above dwell temperature. static U32 ddDwellAtTargetStartMS; ///< Timestamp when the current DD at-target interval started (ms). static U32 wcidDwellAccumulatedMS; ///< Banked WCID dwell time while D4 was above target (ms). static BOOL wcidDwellAtTarget; ///< TRUE while D4 is currently above dwell temperature. static U32 wcidDwellAtTargetStartMS; ///< Timestamp when the current WCID at-target interval started (ms). static BOOL hydroblockHeaterStopped; ///< TRUE after D4 exceeded the hydroblock stop temperature. static BOOL pendingStopDDHeatDisinfectRequest; ///< Flag indicating TD has requested DD stop heat disinfect. static U32 heatDisinfectPublishTimerCounter; ///< Heat disinfect data broadcast timer counter. static OVERRIDE_U32_T heatDisinfectModePublishInterval; ///< Interval at which to publish heat disinfect mode data. // ********** private function prototypes ********** static void setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_STATE_T state ); static void applyDepresPhaseActuators( HEAT_DEPRES_SUB_STATE_T phase ); static void applyDdRecircActuators( void ); static void applyDdRecircLineValves( DD_HEAT_DISINFECT_STATE_T state ); static void stopGearPumps( void ); static void stopConcentrateAndRinsePumps( void ); static BOOL isDdRecircState( DD_HEAT_DISINFECT_STATE_T state ); static BOOL isWcidState( DD_HEAT_DISINFECT_STATE_T state ); static void updateDwell( BOOL *atTarget, U32 *accumMs, U32 *atTargetStartMs, BOOL isAtTarget ); static U32 currentDwellMs( BOOL atTarget, U32 accumMs, U32 atTargetStartMs ); static void resetDdDwell( void ); static void resetWcidDwell( void ); static void manageD65ForBicartRecirc( void ); static void updateWcidDwellFromD4( void ); static U32 ddRecircDurationMs( DD_HEAT_DISINFECT_STATE_T state ); static DD_HEAT_DISINFECT_STATE_T nextDdRecircState( DD_HEAT_DISINFECT_STATE_T state ); static DD_HEAT_DISINFECT_STATE_T handleDepressurizeBicartState( void ); static DD_HEAT_DISINFECT_STATE_T handleDdRecircSetupState( void ); static DD_HEAT_DISINFECT_STATE_T handlePressureCheckState( void ); static DD_HEAT_DISINFECT_STATE_T handleDdRecircState( void ); static DD_HEAT_DISINFECT_STATE_T handleHydroblockRecircState( void ); static DD_HEAT_DISINFECT_STATE_T handleBicartSupplyState( void ); static DD_HEAT_DISINFECT_STATE_T handleBicartVentState( void ); static DD_HEAT_DISINFECT_STATE_T handleWcidDwellCheckState( void ); static void publishHeatDisinfectModeData( void ); /*********************************************************************//** * @brief * The initHeatDisinfectMode function initializes the heat disinfect mode * unit. * @details \b Inputs: none * @details \b Outputs: heatDisinfectState, depressurizeSubState, stateTimerMS, * ddDwellAccumulatedMS, ddDwellAtTarget, ddDwellAtTargetStartMS, * wcidDwellAccumulatedMS, wcidDwellAtTarget, wcidDwellAtTargetStartMS, * hydroblockHeaterStopped, pendingStopDDHeatDisinfectRequest, * heatDisinfectPublishTimerCounter, heatDisinfectModePublishInterval * @return none *************************************************************************/ void initHeatDisinfectMode( void ) { heatDisinfectState = DD_HEAT_DISINFECT_STATE_START; depressurizeSubState = HEAT_DEPRES_WAIT_D6; stateTimerMS = 0; ddDwellAccumulatedMS = 0; ddDwellAtTarget = FALSE; ddDwellAtTargetStartMS = 0; wcidDwellAccumulatedMS = 0; wcidDwellAtTarget = FALSE; wcidDwellAtTargetStartMS = 0; hydroblockHeaterStopped = FALSE; pendingStopDDHeatDisinfectRequest = FALSE; heatDisinfectPublishTimerCounter = 0; heatDisinfectModePublishInterval.data = HEAT_DISINFECT_DATA_PUBLISH_INTERVAL; heatDisinfectModePublishInterval.ovData = HEAT_DISINFECT_DATA_PUBLISH_INTERVAL; heatDisinfectModePublishInterval.ovInitData = 0; heatDisinfectModePublishInterval.override = OVERRIDE_RESET; } /*********************************************************************//** * @brief * The transitionToHeatDisinfectMode function prepares for transition to * heat disinfect mode. * @details \b Inputs: none * @details \b Outputs: heatDisinfectState * @return initial state *************************************************************************/ U32 transitionToHeatDisinfectMode( void ) { initHeatDisinfectMode(); deenergizeActuators( PARK_CONC_PUMPS ); requestBalChamberSwitching( FALSE ); stopHeater( D45_HEAT ); setValveState( D8_VALV, VALVE_STATE_OPEN ); setCurrentSubState( NO_SUB_STATE ); return heatDisinfectState; } /*********************************************************************//** * @brief * The isDdRecircState function returns TRUE for DD line recirc cycle * states. * @details \b Inputs: none * @details \b Outputs: none * @param state heat disinfect execution state * @return TRUE if the state is in the DD recirc cycle *************************************************************************/ static BOOL isDdRecircState( DD_HEAT_DISINFECT_STATE_T state ) { BOOL result = FALSE; if ( ( DD_HEAT_DISINFECT_DD_RECIRC_MAIN == state ) || ( DD_HEAT_DISINFECT_DD_RECIRC_DIALYZER == state ) || ( DD_HEAT_DISINFECT_DD_RECIRC_D100 == state ) || ( DD_HEAT_DISINFECT_DD_RECIRC_D34 == state ) || ( DD_HEAT_DISINFECT_DD_RECIRC_D91 == state ) || ( DD_HEAT_DISINFECT_DD_RECIRC_D76_D47 == state ) ) { result = TRUE; } return result; } /*********************************************************************//** * @brief * The isWcidState function returns TRUE for WCID loop states. * @details \b Inputs: none * @details \b Outputs: none * @param state heat disinfect execution state * @return TRUE if the state is in the WCID loop *************************************************************************/ static BOOL isWcidState( DD_HEAT_DISINFECT_STATE_T state ) { BOOL result = FALSE; if ( ( DD_HEAT_DISINFECT_HYDROBLOCK_RECIRC == state ) || ( DD_HEAT_DISINFECT_BICART_SUPPLY == state ) || ( DD_HEAT_DISINFECT_BICART_VENT == state ) || ( DD_HEAT_DISINFECT_WCID_DWELL_CHECK == state ) ) { result = TRUE; } return result; } /*********************************************************************//** * @brief * The updateDwell function banks dwell time when the sensor leaves the * at-target band, and starts a new interval when it re-enters. * @details \b Inputs: none * @details \b Outputs: none * @param atTarget current at-target flag * @param accumMs banked dwell time (ms) * @param atTargetStartMs start of the current at-target interval (ms) * @param isAtTarget TRUE if the sensor is currently above target * @return none *************************************************************************/ static void updateDwell( BOOL *atTarget, U32 *accumMs, U32 *atTargetStartMs, BOOL isAtTarget ) { U32 nowMs = getMSTimerCount(); if ( TRUE == isAtTarget ) { if ( FALSE == *atTarget ) { *atTarget = TRUE; *atTargetStartMs = nowMs; } } else if ( TRUE == *atTarget ) { *accumMs += calcTimeSince( *atTargetStartMs ); *atTarget = FALSE; } else { // Still below target with no active interval. } } /*********************************************************************//** * @brief * The currentDwellMs function returns banked dwell plus the active * at-target interval. * @details \b Inputs: none * @details \b Outputs: none * @param atTarget TRUE while currently at target * @param accumMs banked dwell time (ms) * @param atTargetStartMs start of the current at-target interval (ms) * @return total dwell time (ms) *************************************************************************/ static U32 currentDwellMs( BOOL atTarget, U32 accumMs, U32 atTargetStartMs ) { U32 dwellMs = accumMs; if ( TRUE == atTarget ) { dwellMs += calcTimeSince( atTargetStartMs ); } return dwellMs; } /*********************************************************************//** * @brief * The resetDdDwell function clears DD recirculation dwell tracking. * @details \b Inputs: none * @details \b Outputs: ddDwellAccumulatedMS, ddDwellAtTarget, ddDwellAtTargetStartMS * @return none *************************************************************************/ static void resetDdDwell( void ) { ddDwellAccumulatedMS = 0u; ddDwellAtTarget = FALSE; ddDwellAtTargetStartMS = 0u; } /*********************************************************************//** * @brief * The resetWcidDwell function clears WCID dwell tracking. * @details \b Inputs: none * @details \b Outputs: wcidDwellAccumulatedMS, wcidDwellAtTarget, wcidDwellAtTargetStartMS * @return none *************************************************************************/ static void resetWcidDwell( void ) { wcidDwellAccumulatedMS = 0u; wcidDwellAtTarget = FALSE; wcidDwellAtTargetStartMS = 0u; } /*********************************************************************//** * @brief * The applyDdRecircLineValves function sets the DD recirc cycle valves. * @details \b Inputs: none * @details \b Outputs: none * @param state current DD recirc state * @return none *************************************************************************/ static void applyDdRecircLineValves( DD_HEAT_DISINFECT_STATE_T state ) { setValveState( D34_VALV, VALVE_STATE_CLOSED ); setValveState( D91_VALV, VALVE_STATE_CLOSED ); setValveState( D35_VALV, VALVE_STATE_CLOSED ); setValveState( D40_VALV, VALVE_STATE_CLOSED ); setValveState( D100_VALV, VALVE_STATE_CLOSED ); setValveState( D47_VALV, VALVE_STATE_CLOSED ); requestConcentratePumpOff( D76_PUMP, FALSE ); switch ( state ) { case DD_HEAT_DISINFECT_DD_RECIRC_MAIN: setValveState( D34_VALV, VALVE_STATE_OPEN ); setValveState( D91_VALV, VALVE_STATE_OPEN ); setValveState( D35_VALV, VALVE_STATE_OPEN ); setValveState( D40_VALV, VALVE_STATE_OPEN ); break; case DD_HEAT_DISINFECT_DD_RECIRC_DIALYZER: setValveState( D35_VALV, VALVE_STATE_OPEN ); setValveState( D40_VALV, VALVE_STATE_OPEN ); break; case DD_HEAT_DISINFECT_DD_RECIRC_D100: setValveState( D35_VALV, VALVE_STATE_OPEN ); setValveState( D100_VALV, VALVE_STATE_OPEN ); break; case DD_HEAT_DISINFECT_DD_RECIRC_D34: setValveState( D34_VALV, VALVE_STATE_OPEN ); break; case DD_HEAT_DISINFECT_DD_RECIRC_D91: setValveState( D91_VALV, VALVE_STATE_OPEN ); break; case DD_HEAT_DISINFECT_DD_RECIRC_D76_D47: setValveState( D35_VALV, VALVE_STATE_OPEN ); setValveState( D34_VALV, VALVE_STATE_OPEN ); setValveState( D40_VALV, VALVE_STATE_OPEN ); setValveState( D91_VALV, VALVE_STATE_OPEN ); setValveState( D47_VALV, VALVE_STATE_OPEN ); setConcentratePumpTargetSpeed( D76_PUMP, HEAT_DISINFECT_CONC_FLUSH_SPEED_ML_MIN, DOSING_CONT_VOLUME ); requestConcentratePumpOn( D76_PUMP ); break; default: break; } } /*********************************************************************//** * @brief * The applyDdRecircActuators function configures shared DD recirc pumps * and valves. * @details \b Inputs: none * @details \b Outputs: none * @return none *************************************************************************/ static void applyDdRecircActuators( void ) { requestBalChamberSwitching( FALSE ); valveControlForBCOpenState(); stopHeater( D45_HEAT ); setValveState( D52_VALV, VALVE_STATE_OPEN ); setValveState( D3_VALV, VALVE_STATE_OPEN ); setValveState( D14_VALV, VALVE_STATE_OPEN ); setValveState( D8_VALV, VALVE_STATE_OPEN ); setValveState( D54_VALV, VALVE_STATE_OPEN ); setValveState( D83_VALV, VALVE_STATE_OPEN ); //TODO: Remove after testing setValveState( M12_VALV, VALVE_STATE_CLOSED ); setValveState( P39_VALV, VALVE_STATE_CLOSED ); setValveState( D35_VALV, VALVE_STATE_CLOSED ); setValveState( D40_VALV, VALVE_STATE_CLOSED ); setValveState( D34_VALV, VALVE_STATE_CLOSED ); setValveState( D91_VALV, VALVE_STATE_CLOSED ); setValveState( D95_VALV, VALVE_STATE_CLOSED ); setValveState( D31_VALV, VALVE_STATE_CLOSED ); setValveState( D47_VALV, VALVE_STATE_CLOSED ); setValveState( D88_79_VALV, VALVE_STATE_CLOSED ); setValveState( D100_VALV, VALVE_STATE_CLOSED ); setValveState( D64_VALV, VALVE_STATE_CLOSED ); setValveState( D65_VALV, VALVE_STATE_CLOSED ); setValveState( D80_VALV, VALVE_STATE_CLOSED ); setValveState( D81_VALV, VALVE_STATE_CLOSED ); setValveState( D85_VALV, VALVE_STATE_CLOSED ); setRinsePumpState( RINSE_PUMP_STATE_ON ); setConcentratePumpTargetSpeed( D10_PUMP, HEAT_DISINFECT_CONC_FLUSH_SPEED_ML_MIN, DOSING_CONT_VOLUME ); setConcentratePumpTargetSpeed( D11_PUMP, HEAT_DISINFECT_CONC_FLUSH_SPEED_ML_MIN, DOSING_CONT_VOLUME ); setSubstitutionPumpTargetRate( D92_PUMP, HEAT_DISINFECT_HDF_PUMP_SPEED_ML_MIN ); requestConcentratePumpOn( D10_PUMP ); requestConcentratePumpOn( D11_PUMP ); setDialysatePumpTargetRPM( D12_PUMP, HEAT_DISINFECT_D12_RECIRC_RPM, TRUE ); setDialysatePumpTargetRPM( D48_PUMP, HEAT_DISINFECT_D48_RECIRC_RPM, TRUE ); } /*********************************************************************//** * @brief * The applyDepresPhaseActuators function applies bicart depressurize * valves. D64 and D80 remain open when leaving this sequence. * @details \b Inputs: none * @details \b Outputs: none * @param phase current depressurize phase * @return none *************************************************************************/ static void applyDepresPhaseActuators( HEAT_DEPRES_SUB_STATE_T phase ) { setValveState( D8_VALV, VALVE_STATE_OPEN ); switch ( phase ) { case HEAT_DEPRES_WAIT_D6: setValveState( D64_VALV, VALVE_STATE_CLOSED ); setValveState( D80_VALV, VALVE_STATE_CLOSED ); setValveState( D65_VALV, VALVE_STATE_CLOSED ); break; case HEAT_DEPRES_OPEN_D64: setValveState( D3_VALV, VALVE_STATE_CLOSED ); setValveState( D64_VALV, VALVE_STATE_OPEN ); setValveState( D80_VALV, VALVE_STATE_OPEN ); setValveState( D65_VALV, VALVE_STATE_CLOSED ); break; case HEAT_DEPRES_OPEN_D80: setValveState( D3_VALV, VALVE_STATE_CLOSED ); setValveState( D64_VALV, VALVE_STATE_OPEN ); setValveState( D80_VALV, VALVE_STATE_OPEN ); setValveState( D65_VALV, VALVE_STATE_OPEN ); break; default: break; } } /*********************************************************************//** * @brief * The setModeHeatDisinfectStateTransition function sets the actuators * and variables for the state transition in heat disinfect mode. * @details \b Inputs: heatDisinfectState * @details \b Outputs: stateTimerMS, depressurizeSubState, hydroblockHeaterStopped, * ddDwellAccumulatedMS, ddDwellAtTarget, ddDwellAtTargetStartMS, * wcidDwellAccumulatedMS, wcidDwellAtTarget, wcidDwellAtTargetStartMS * @details \b Alarm: ALARM_ID_DD_SOFTWARE_FAULT when an invalid heat disinfect * state is requested. * @param state heat disinfect state enum * @return none *************************************************************************/ static void setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_STATE_T state ) { stateTimerMS = getMSTimerCount(); switch ( state ) { case DD_HEAT_DISINFECT_STATE_START: requestBalChamberSwitching( FALSE ); setValveState( D8_VALV, VALVE_STATE_OPEN ); break; case DD_HEAT_DISINFECT_DEPRESSURIZE_BICART: requestBalChamberSwitching( FALSE ); stopHeater( D5_HEAT ); stopHeater( D45_HEAT ); stopGearPumps(); stopConcentrateAndRinsePumps(); depressurizeSubState = HEAT_DEPRES_WAIT_D6; applyDepresPhaseActuators( HEAT_DEPRES_WAIT_D6 ); break; case DD_HEAT_DISINFECT_DD_RECIRC_SETUP: resetDdDwell(); applyDdRecircActuators(); applyDdRecircLineValves( DD_HEAT_DISINFECT_DD_RECIRC_MAIN ); stopHeater( D5_HEAT ); break; case DD_HEAT_DISINFECT_PRESSURE_CHECK: applyDdRecircActuators(); applyDdRecircLineValves( DD_HEAT_DISINFECT_DD_RECIRC_MAIN ); stopHeater( D5_HEAT ); break; case DD_HEAT_DISINFECT_DD_RECIRC_MAIN: case DD_HEAT_DISINFECT_DD_RECIRC_DIALYZER: case DD_HEAT_DISINFECT_DD_RECIRC_D100: case DD_HEAT_DISINFECT_DD_RECIRC_D34: case DD_HEAT_DISINFECT_DD_RECIRC_D91: case DD_HEAT_DISINFECT_DD_RECIRC_D76_D47: if ( FALSE == isDdRecircState( heatDisinfectState ) ) { applyDdRecircActuators(); } applyDdRecircLineValves( state ); setHeaterTargetTemperature( D5_HEAT, HEAT_DISINFECT_HEATER_TARGET_C ); startHeater( D5_HEAT ); stopHeater( D45_HEAT ); break; case DD_HEAT_DISINFECT_HYDROBLOCK_RECIRC: if ( FALSE == isWcidState( heatDisinfectState ) ) { resetWcidDwell(); } hydroblockHeaterStopped = FALSE; requestBalChamberSwitching( FALSE ); valveControlForBCClosedState(); stopConcentrateAndRinsePumps(); signalDialysatePumpHardStop( D48_PUMP ); stopHeater( D45_HEAT ); setValveState( D54_VALV, VALVE_STATE_CLOSED ); setValveState( D52_VALV, VALVE_STATE_CLOSED ); setValveState( D14_VALV, VALVE_STATE_CLOSED ); setValveState( D3_VALV, VALVE_STATE_CLOSED ); setValveState( D8_VALV, VALVE_STATE_OPEN ); setValveState( D64_VALV, VALVE_STATE_CLOSED ); setValveState( D65_VALV, VALVE_STATE_CLOSED ); setValveState( D80_VALV, VALVE_STATE_CLOSED ); setValveState( D85_VALV, VALVE_STATE_CLOSED ); setValveState( D83_VALV, VALVE_STATE_CLOSED ); setDialysatePumpTargetRPM( D12_PUMP, HEAT_DISINFECT_D12_HYDROBLOCK_RPM, TRUE ); setHeaterTargetTemperature( D5_HEAT, HEAT_DISINFECT_HYDROBLOCK_TARGET_C ); startHeater( D5_HEAT ); break; case DD_HEAT_DISINFECT_BICART_SUPPLY: requestBalChamberSwitching( FALSE ); valveControlForBCClosedState(); stopConcentrateAndRinsePumps(); stopHeater( D5_HEAT ); stopHeater( D45_HEAT ); setValveState( D8_VALV, VALVE_STATE_OPEN ); setValveState( D14_VALV, VALVE_STATE_CLOSED ); setValveState( D64_VALV, VALVE_STATE_OPEN ); setValveState( D80_VALV, VALVE_STATE_OPEN ); setValveState( D65_VALV, VALVE_STATE_CLOSED ); setValveState( D85_VALV, VALVE_STATE_CLOSED ); setDialysatePumpTargetRPM( D12_PUMP, HEAT_DISINFECT_D12_POST_RECIRC_RPM, TRUE ); break; case DD_HEAT_DISINFECT_BICART_VENT: setValveState( D8_VALV, VALVE_STATE_OPEN ); setValveState( D64_VALV, VALVE_STATE_OPEN ); setValveState( D85_VALV, VALVE_STATE_OPEN ); setValveState( D80_VALV, VALVE_STATE_CLOSED ); break; case DD_HEAT_DISINFECT_WCID_DWELL_CHECK: stopConcentrateAndRinsePumps(); stopHeater( D5_HEAT ); setValveState( D8_VALV, VALVE_STATE_OPEN ); setDialysatePumpTargetRPM( D12_PUMP, HEAT_DISINFECT_D12_DWELL_CHECK_RPM, TRUE ); break; case DD_HEAT_DISINFECT_STATE_COMPLETE: deenergizeActuators( PARK_CONC_PUMPS ); requestBalChamberSwitching( FALSE ); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_HEAT_DISINFECT_MODE_INVALID_EXEC_STATE, state ) break; } } /*********************************************************************//** * @brief * The stopGearPumps function hard-stops D12 and D48. * @details \b Inputs: none * @details \b Outputs: none * @return none *************************************************************************/ static void stopGearPumps( void ) { signalDialysatePumpHardStop( D12_PUMP ); signalDialysatePumpHardStop( D48_PUMP ); } /*********************************************************************//** * @brief * The stopConcentrateAndRinsePumps function stops D10, D11, D76, and the * rinse pump. * @details \b Inputs: none * @details \b Outputs: none * @return none *************************************************************************/ static void stopConcentrateAndRinsePumps( void ) { requestConcentratePumpOff( D10_PUMP, FALSE ); requestConcentratePumpOff( D11_PUMP, FALSE ); requestConcentratePumpOff( D76_PUMP, FALSE ); setRinsePumpState( RINSE_PUMP_STATE_OFF ); } /*********************************************************************//** * @brief * The manageD65ForBicartRecirc function flutters D65 during bicart supply * and vent recirculation. D65 opens when D66 is below 3 PSI and closes * when D66 is above 3 PSI. * @details \b Inputs: none * @details \b Outputs: none * @return none *************************************************************************/ static void manageD65ForBicartRecirc( void ) { F32 d66Psi = getFilteredPressure( D66_PRES ); if ( d66Psi > HEAT_DISINFECT_D66_TARGET_PSI ) { setValveState( D65_VALV, VALVE_STATE_CLOSED ); } else if ( d66Psi < HEAT_DISINFECT_D66_TARGET_PSI ) { setValveState( D65_VALV, VALVE_STATE_OPEN ); } else { // Hold the current D65 state at 3 PSI. } } /*********************************************************************//** * @brief * The updateWcidDwellFromD4 function accumulates WCID dwell while D4 is * above 80 C. * @details \b Inputs: wcidDwellAtTarget, wcidDwellAccumulatedMS, wcidDwellAtTargetStartMS * @details \b Outputs: wcidDwellAtTarget, wcidDwellAccumulatedMS, wcidDwellAtTargetStartMS * @return none *************************************************************************/ static void updateWcidDwellFromD4( void ) { BOOL d4AtTarget = FALSE; if ( getFilteredTemperatureValue( D4_TEMP ) > HEAT_DISINFECT_D4_DWELL_TEMP_C ) { d4AtTarget = TRUE; } updateDwell( &wcidDwellAtTarget, &wcidDwellAccumulatedMS, &wcidDwellAtTargetStartMS, d4AtTarget ); } /*********************************************************************//** * @brief * The ddRecircDurationMs function returns the required time in a DD line * recirc state. * @details \b Inputs: none * @details \b Outputs: none * @param state current DD recirc state * @return duration (ms) *************************************************************************/ static U32 ddRecircDurationMs( DD_HEAT_DISINFECT_STATE_T state ) { U32 durationMs = HEAT_DISINFECT_LINE_RECIRC_MS; if ( DD_HEAT_DISINFECT_DD_RECIRC_D76_D47 == state ) { durationMs = HEAT_DISINFECT_D76_D47_RECIRC_MS; } return durationMs; } /*********************************************************************//** * @brief * The nextDdRecircState function returns the next DD line recirc state. * @details \b Inputs: none * @details \b Outputs: none * @param state current DD recirc state * @return next DD recirc state *************************************************************************/ static DD_HEAT_DISINFECT_STATE_T nextDdRecircState( DD_HEAT_DISINFECT_STATE_T state ) { DD_HEAT_DISINFECT_STATE_T next = DD_HEAT_DISINFECT_DD_RECIRC_MAIN; switch ( state ) { case DD_HEAT_DISINFECT_DD_RECIRC_MAIN: next = DD_HEAT_DISINFECT_DD_RECIRC_DIALYZER; break; case DD_HEAT_DISINFECT_DD_RECIRC_DIALYZER: next = DD_HEAT_DISINFECT_DD_RECIRC_D100; break; case DD_HEAT_DISINFECT_DD_RECIRC_D100: next = DD_HEAT_DISINFECT_DD_RECIRC_D34; break; case DD_HEAT_DISINFECT_DD_RECIRC_D34: next = DD_HEAT_DISINFECT_DD_RECIRC_D91; break; case DD_HEAT_DISINFECT_DD_RECIRC_D91: next = DD_HEAT_DISINFECT_DD_RECIRC_D76_D47; break; case DD_HEAT_DISINFECT_DD_RECIRC_D76_D47: next = DD_HEAT_DISINFECT_DD_RECIRC_MAIN; break; default: next = DD_HEAT_DISINFECT_DD_RECIRC_MAIN; break; } return next; } /*********************************************************************//** * @brief * The handleDepressurizeBicartState function waits for D6 full, then * depressurizes the bicart. D64 opens, D80 opens after 100 ms, D65 closes * after 1 s. D64 and D80 stay open on exit. * @details \b Inputs: depressurizeSubState, stateTimerMS * @details \b Outputs: depressurizeSubState, stateTimerMS * @return the current state of heat disinfect mode *************************************************************************/ static DD_HEAT_DISINFECT_STATE_T handleDepressurizeBicartState( void ) { DD_HEAT_DISINFECT_STATE_T state = DD_HEAT_DISINFECT_DEPRESSURIZE_BICART; switch ( depressurizeSubState ) { case HEAT_DEPRES_WAIT_D6: if ( LEVEL_STATE_HIGH == getLevelStatus( D6_LEVL ) ) { setValveState( D3_VALV, VALVE_STATE_CLOSED ); depressurizeSubState = HEAT_DEPRES_OPEN_D64; stateTimerMS = getMSTimerCount(); applyDepresPhaseActuators( HEAT_DEPRES_OPEN_D64 ); } else { setValveState( D3_VALV, VALVE_STATE_OPEN ); } break; case HEAT_DEPRES_OPEN_D64: if ( TRUE == didTimeout( stateTimerMS, HEAT_DISINFECT_DEPRES_D80_DELAY_MS ) ) { depressurizeSubState = HEAT_DEPRES_OPEN_D80; stateTimerMS = getMSTimerCount(); applyDepresPhaseActuators( HEAT_DEPRES_OPEN_D80 ); } break; case HEAT_DEPRES_OPEN_D80: if ( TRUE == didTimeout( stateTimerMS, HEAT_DISINFECT_DEPRES_D65_OPEN_MS ) ) { setValveState( D65_VALV, VALVE_STATE_CLOSED ); setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_DD_RECIRC_SETUP ); state = DD_HEAT_DISINFECT_DD_RECIRC_SETUP; } break; default: depressurizeSubState = HEAT_DEPRES_WAIT_D6; stateTimerMS = getMSTimerCount(); break; } return state; } /*********************************************************************//** * @brief * The handleDdRecircSetupState function holds DD recirc setup until the * 2 minute stability period completes. * @details \b Inputs: stateTimerMS * @details \b Outputs: none * @return the current state of heat disinfect mode *************************************************************************/ static DD_HEAT_DISINFECT_STATE_T handleDdRecircSetupState( void ) { DD_HEAT_DISINFECT_STATE_T state = DD_HEAT_DISINFECT_DD_RECIRC_SETUP; if ( TRUE == didTimeout( stateTimerMS, HEAT_DISINFECT_SETUP_STABILITY_MS ) ) { setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_PRESSURE_CHECK ); state = DD_HEAT_DISINFECT_PRESSURE_CHECK; } return state; } /*********************************************************************//** * @brief * The handlePressureCheckState function starts D5 after D18 exceeds 3 PSI * and enters the DD line recirc cycle. * @details \b Inputs: none * @details \b Outputs: none * @return the current state of heat disinfect mode *************************************************************************/ static DD_HEAT_DISINFECT_STATE_T handlePressureCheckState( void ) { DD_HEAT_DISINFECT_STATE_T state = DD_HEAT_DISINFECT_PRESSURE_CHECK; if ( getFilteredPressure( D18_PRES ) > HEAT_DISINFECT_D18_HEATER_ENABLE_PSI ) { setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_DD_RECIRC_MAIN ); state = DD_HEAT_DISINFECT_DD_RECIRC_MAIN; } return state; } /*********************************************************************//** * @brief * The handleDdRecircState function cycles DD line recirc states and * accumulates D78 dwell. * @details \b Inputs: heatDisinfectState, stateTimerMS, ddDwellAtTarget, * ddDwellAccumulatedMS, ddDwellAtTargetStartMS * @details \b Outputs: ddDwellAtTarget, ddDwellAccumulatedMS, ddDwellAtTargetStartMS * @return the current state of heat disinfect mode *************************************************************************/ static DD_HEAT_DISINFECT_STATE_T handleDdRecircState( void ) { DD_HEAT_DISINFECT_STATE_T state = heatDisinfectState; BOOL d78AtTarget = FALSE; U32 dwellMs; if ( getFilteredTemperatureValue( D78_TEMP ) > HEAT_DISINFECT_D78_DWELL_TEMP_C ) { d78AtTarget = TRUE; } updateDwell( &ddDwellAtTarget, &ddDwellAccumulatedMS, &ddDwellAtTargetStartMS, d78AtTarget ); dwellMs = currentDwellMs( ddDwellAtTarget, ddDwellAccumulatedMS, ddDwellAtTargetStartMS ); if ( dwellMs >= HEAT_DISINFECT_DWELL_TIME_MS ) { setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_HYDROBLOCK_RECIRC ); state = DD_HEAT_DISINFECT_HYDROBLOCK_RECIRC; } else if ( TRUE == didTimeout( stateTimerMS, ddRecircDurationMs( heatDisinfectState ) ) ) { DD_HEAT_DISINFECT_STATE_T nextState = nextDdRecircState( heatDisinfectState ); setModeHeatDisinfectStateTransition( nextState ); state = nextState; } else { // Remain in the current line recirc state. } return state; } /*********************************************************************//** * @brief * The handleHydroblockRecircState function heats until D4 is at or above * 88 C, then enters bicart supply. * @details \b Inputs: hydroblockHeaterStopped, wcidDwellAtTarget, * wcidDwellAccumulatedMS, wcidDwellAtTargetStartMS * @details \b Outputs: hydroblockHeaterStopped, wcidDwellAtTarget, * wcidDwellAccumulatedMS, wcidDwellAtTargetStartMS * @return the current state of heat disinfect mode *************************************************************************/ static DD_HEAT_DISINFECT_STATE_T handleHydroblockRecircState( void ) { DD_HEAT_DISINFECT_STATE_T state = DD_HEAT_DISINFECT_HYDROBLOCK_RECIRC; updateWcidDwellFromD4(); if ( FALSE == hydroblockHeaterStopped ) { if ( getFilteredTemperatureValue( D4_TEMP ) >= HEAT_DISINFECT_HYDROBLOCK_STOP_TEMP_C ) { hydroblockHeaterStopped = TRUE; setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_BICART_SUPPLY ); state = DD_HEAT_DISINFECT_BICART_SUPPLY; } } else { setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_BICART_SUPPLY ); state = DD_HEAT_DISINFECT_BICART_SUPPLY; } return state; } /*********************************************************************//** * @brief * The handleBicartSupplyState function runs supply recirc for 6 s and * flutters D65 around 3 PSI. * @details \b Inputs: stateTimerMS, wcidDwellAtTarget, wcidDwellAccumulatedMS, * wcidDwellAtTargetStartMS * @details \b Outputs: wcidDwellAtTarget, wcidDwellAccumulatedMS, wcidDwellAtTargetStartMS * @return the current state of heat disinfect mode *************************************************************************/ static DD_HEAT_DISINFECT_STATE_T handleBicartSupplyState( void ) { DD_HEAT_DISINFECT_STATE_T state = DD_HEAT_DISINFECT_BICART_SUPPLY; updateWcidDwellFromD4(); manageD65ForBicartRecirc(); if ( TRUE == didTimeout( stateTimerMS, HEAT_DISINFECT_BICART_SUPPLY_MS ) ) { setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_BICART_VENT ); state = DD_HEAT_DISINFECT_BICART_VENT; } return state; } /*********************************************************************//** * @brief * The handleBicartVentState function runs vent recirc for 6 s and * flutters D65 around 3 PSI. * @details \b Inputs: stateTimerMS, wcidDwellAtTarget, wcidDwellAccumulatedMS, * wcidDwellAtTargetStartMS * @details \b Outputs: wcidDwellAtTarget, wcidDwellAccumulatedMS, wcidDwellAtTargetStartMS * @return the current state of heat disinfect mode *************************************************************************/ static DD_HEAT_DISINFECT_STATE_T handleBicartVentState( void ) { DD_HEAT_DISINFECT_STATE_T state = DD_HEAT_DISINFECT_BICART_VENT; updateWcidDwellFromD4(); manageD65ForBicartRecirc(); if ( TRUE == didTimeout( stateTimerMS, HEAT_DISINFECT_BICART_VENT_MS ) ) { setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_WCID_DWELL_CHECK ); state = DD_HEAT_DISINFECT_WCID_DWELL_CHECK; } return state; } /*********************************************************************//** * @brief * The handleWcidDwellCheckState function mixes with D5 off for 6 s, then * completes if D4 dwell is met or returns to hydroblock. * @details \b Inputs: stateTimerMS, wcidDwellAtTarget, wcidDwellAccumulatedMS, * wcidDwellAtTargetStartMS * @details \b Outputs: wcidDwellAtTarget, wcidDwellAccumulatedMS, wcidDwellAtTargetStartMS * @return the current state of heat disinfect mode *************************************************************************/ static DD_HEAT_DISINFECT_STATE_T handleWcidDwellCheckState( void ) { DD_HEAT_DISINFECT_STATE_T state = DD_HEAT_DISINFECT_WCID_DWELL_CHECK; U32 dwellMs; updateWcidDwellFromD4(); dwellMs = currentDwellMs( wcidDwellAtTarget, wcidDwellAccumulatedMS, wcidDwellAtTargetStartMS ); if ( TRUE == didTimeout( stateTimerMS, HEAT_DISINFECT_WCID_DWELL_CHECK_MS ) ) { if ( dwellMs >= HEAT_DISINFECT_DWELL_TIME_MS ) { setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_STATE_COMPLETE ); state = DD_HEAT_DISINFECT_STATE_COMPLETE; } else { setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_HYDROBLOCK_RECIRC ); state = DD_HEAT_DISINFECT_HYDROBLOCK_RECIRC; } } return state; } /*********************************************************************//** * @brief * The execHeatDisinfectMode function executes the heat disinfect mode * state machine. * @details \b Inputs: pendingStopDDHeatDisinfectRequest, heatDisinfectState * @details \b Outputs: pendingStopDDHeatDisinfectRequest, heatDisinfectState * @details \b Alarm: ALARM_ID_DD_SOFTWARE_FAULT when wrong heat disinfect * state invoked. * @return current state *************************************************************************/ U32 execHeatDisinfectMode( void ) { if ( TRUE == pendingStopDDHeatDisinfectRequest ) { pendingStopDDHeatDisinfectRequest = FALSE; setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_STATE_COMPLETE ); heatDisinfectState = DD_HEAT_DISINFECT_STATE_COMPLETE; requestNewOperationMode( DD_MODE_STAN ); } else { switch ( heatDisinfectState ) { case DD_HEAT_DISINFECT_STATE_START: setModeHeatDisinfectStateTransition( DD_HEAT_DISINFECT_DEPRESSURIZE_BICART ); heatDisinfectState = DD_HEAT_DISINFECT_DEPRESSURIZE_BICART; break; case DD_HEAT_DISINFECT_DEPRESSURIZE_BICART: heatDisinfectState = handleDepressurizeBicartState(); break; case DD_HEAT_DISINFECT_DD_RECIRC_SETUP: heatDisinfectState = handleDdRecircSetupState(); break; case DD_HEAT_DISINFECT_PRESSURE_CHECK: heatDisinfectState = handlePressureCheckState(); break; case DD_HEAT_DISINFECT_DD_RECIRC_MAIN: case DD_HEAT_DISINFECT_DD_RECIRC_DIALYZER: case DD_HEAT_DISINFECT_DD_RECIRC_D100: case DD_HEAT_DISINFECT_DD_RECIRC_D34: case DD_HEAT_DISINFECT_DD_RECIRC_D91: case DD_HEAT_DISINFECT_DD_RECIRC_D76_D47: heatDisinfectState = handleDdRecircState(); break; case DD_HEAT_DISINFECT_HYDROBLOCK_RECIRC: heatDisinfectState = handleHydroblockRecircState(); break; case DD_HEAT_DISINFECT_BICART_SUPPLY: heatDisinfectState = handleBicartSupplyState(); break; case DD_HEAT_DISINFECT_BICART_VENT: heatDisinfectState = handleBicartVentState(); break; case DD_HEAT_DISINFECT_WCID_DWELL_CHECK: heatDisinfectState = handleWcidDwellCheckState(); break; case DD_HEAT_DISINFECT_STATE_COMPLETE: requestNewOperationMode( DD_MODE_STAN ); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_HEAT_DISINFECT_MODE_INVALID_EXEC_STATE, heatDisinfectState ) heatDisinfectState = DD_HEAT_DISINFECT_STATE_START; break; } } publishHeatDisinfectModeData(); return heatDisinfectState; } /*********************************************************************//** * @brief * The getCurrentHeatDisinfectState function returns the current heat * disinfect state. * @details \b Inputs: heatDisinfectState * @details \b Outputs: none * @return current heat disinfect state *************************************************************************/ DD_HEAT_DISINFECT_STATE_T getCurrentHeatDisinfectState( void ) { return heatDisinfectState; } /*********************************************************************//** * @brief * The requestDDHeatDisinfectStop function handles a request to stop heat * disinfect. * @details \b Inputs: none * @details \b Outputs: pendingStopDDHeatDisinfectRequest * @return TRUE if the request was accepted, FALSE if not *************************************************************************/ BOOL requestDDHeatDisinfectStop( void ) { BOOL result = FALSE; if ( DD_MODE_HEAT == getCurrentOperationMode() ) { pendingStopDDHeatDisinfectRequest = TRUE; result = TRUE; } return result; } /*********************************************************************//** * @brief * The publishHeatDisinfectModeData function broadcasts heat disinfect * mode data at the configured interval. * @details \b Inputs: heatDisinfectPublishTimerCounter, heatDisinfectModePublishInterval, * ddDwellAtTarget, ddDwellAccumulatedMS, ddDwellAtTargetStartMS, * wcidDwellAtTarget, wcidDwellAccumulatedMS, wcidDwellAtTargetStartMS * @details \b Outputs: heatDisinfectPublishTimerCounter * @details \b Message \Sent: MSG_ID_DD_HEAT_DISINFECT_STATE_DATA to publish the * heat disinfect mode data. * @return none *************************************************************************/ static void publishHeatDisinfectModeData( void ) { if ( ++heatDisinfectPublishTimerCounter >= getU32OverrideValue( &heatDisinfectModePublishInterval ) ) { HEAT_DISINFECT_STATE_DATA_T data; data.heatDisinfectExecState = (U32)getCurrentHeatDisinfectState(); data.ddRecircDwellTimeMS = currentDwellMs( ddDwellAtTarget, ddDwellAccumulatedMS, ddDwellAtTargetStartMS ); data.wcidRecircDwellTimeMS = currentDwellMs( wcidDwellAtTarget, wcidDwellAccumulatedMS, wcidDwellAtTargetStartMS ); broadcastData( MSG_ID_DD_HEAT_DISINFECT_STATE_DATA, COMM_BUFFER_OUT_CAN_DD_BROADCAST, (U08*)&data, sizeof( HEAT_DISINFECT_STATE_DATA_T ) ); heatDisinfectPublishTimerCounter = 0; } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testDDHeatDisinfectDataPublishIntervalOverride function overrides * the heat disinfect mode data publish interval. * @details \b Inputs: heatDisinfectModePublishInterval * @details \b Outputs: heatDisinfectModePublishInterval * @param message Dial-in override message * @return TRUE if override successful *************************************************************************/ BOOL testDDHeatDisinfectDataPublishIntervalOverride( MESSAGE_T *message ) { return u32BroadcastIntervalOverride( message, &heatDisinfectModePublishInterval, TASK_GENERAL_INTERVAL ); } /**@}*/