/************************************************************************** * * Copyright (c) 2020-2023 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) Dara Navaei * @date (last) 28-Sep-2023 * * @author (original) Sean * @date (original) 20-Apr-2020 * ***************************************************************************/ #include // For fabs #include "ConcentratePumps.h" #include "ConductivitySensors.h" #include "CPLD.h" #include "DrainPump.h" #include "Heaters.h" #include "LoadCell.h" #include "MessageSupport.h" #include "ModeFault.h" #include "ModeHeatDisinfect.h" #include "NVDataMgmt.h" #include "OperationModes.h" #include "Pressures.h" #include "Reservoirs.h" #include "ROPump.h" #include "RTC.h" #include "Switches.h" #include "SystemCommMessages.h" #include "TaskGeneral.h" #include "TemperatureSensors.h" #include "Timers.h" #include "UVReactors.h" #include "Valves.h" /** * @addtogroup DGHeatDisinfectMode * @{ */ // ********** private definitions ********** // General defines #define MAX_ALLOWED_STATE_TRIALS 1 ///< Max allowed trials on a state. This is general among all the states. #define HEAT_DISINFECT_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Mode heat disinfect data publish interval in counts. // Drain R1 & R2 states defines #define DRAIN_PUMP_TARGET_RPM 2400 ///< Drain pump target RPM during drain. #define RSRVRS_INITIAL_DRAIN_TIMEOUT_MS ( 2 * SEC_PER_MIN * MS_PER_SECOND ) ///< Reservoirs 1 & 2 initial drain time out in milliseconds. #define DRAIN_WEIGHT_UNCHANGE_TIMEOUT_MS ( 6 * MS_PER_SECOND ) ///< Time period of unchanged weight during draining before timeout. // Flush drain path state defines #define FLUSH_DRAIN_WAIT_TIME_MS ( 2 * SEC_PER_MIN * MS_PER_SECOND ) ///< Flush Drain path wait time in milliseconds. // Flush circulation path state defines #define RO_PUMP_TARGET_FLUSH_FILL_FLOW_RATE_LPM 0.8F ///< RO pump target flow rate during flush/fill in L/min. #define MAX_RO_PUMP_FLUSH_FILL_PRESSURE_PSI 130 ///< Maximum RO pump pressure during flush/fill states in psi. #define FLUSH_CICRCULATION_WAIT_TIME_MS ( 60 * MS_PER_SECOND ) ///< Flush circulation path wait time in milliseconds. #define FLUSH_CIRCULATION_CONC_PUMPS_WAIT_TIME_MS ( 3 * SEC_PER_MIN * MS_PER_SECOND ) ///< Flush circulation concentrate pumps on time in milliseconds. #define MAX_FLUSH_CIRC_TEMP_SENSOR_DIFF_C 3.0F ///< Maximum flush circulation temperature difference tolerance in C. #define NUM_OF_TEMP_SENSORS_TO_AVG 3.0F ///< Number of temperature sensors to average to check the difference. #define ACID_PUMP_FLUSH_RECIRCULATE_SPEED_MLPM -30.0F ///< Acid pump in flush recirculate speed in mL/min. #define BICARB_PUMP_FLUSH_RECIRCULATE_SPEED_MLPM 30.6F ///< Bicarb pump in flush recirculate speed in mL/min. #define ACID_PUMP_SPEED_ML_PER_MIN 30.6F ///< Acid concentrate pump speed in mL/min. // The acid pump is 2% faster than the bicarb pump to create a flow from acid to bicarb line during heat disinfect #define BICARB_PUMP_SPEED_ML_PER_MIN -30.0F ///< Bicarb concentrate pump speed in mL/min. // Flush and drain R1 and R2 #define RSRVRS_FULL_VOL_ML 1850.0F ///< Reservoirs 1 & 2 full volume in mL. #define RSRVRS_PARTIAL_FILL_VOL_ML 500.0F ///< Reservoirs 1 & 2 partial volume in mL. #define RSRVRS_FULL_STABLE_TIME_COUNT ( ( 4 * MS_PER_SECOND ) / TASK_GENERAL_INTERVAL ) ///< Reservoirs 1 & 2 full stable time in counts. #define RSRVRS_FILL_UP_TIMEOUT_MS ( 5 * SEC_PER_MIN * MS_PER_SECOND ) ///< Reservoirs 1 & 2 full fill up timeout in ms. #define RSRVRS_500ML_FILL_UP_TIMEOUT_MS ( 2 * SEC_PER_MIN * MS_PER_SECOND ) ///< Reservoirs 1 & 2 partial fill up timeout in ms. #define RSRVRS_DRAIN_TIMEOUT_MS ( 2 * SEC_PER_MIN * MS_PER_SECOND ) ///< Reservoirs 1 & 2 drain timeout in ms. // Target temperature #define HEAT_DISINFECT_PRIM_HEATER_FLUSH_TARGET_TEMP_C 60.0F ///< Heat disinfect primary heater flush target temperature C. #define HEAT_DISINFECT_PRIM_HEATER_TARGET_TEMP_C 90.0F ///< Heat disinfect primary heater target temperature in C. #define HEAT_DISINFECT_TRIM_HEATER_TARGET_TEMP_C 90.0F ///< Heat disinfect trimmer heater target temperature in C. #define HEAT_DISINFECT_CONC_PUMPS_START_TEMP_C 75.0F ///< Heat disinfect start concentrate pumps target temperature in C. #define HEAT_DISINFECT_TRIMMER_HEATER_STOP_TEMP_C 75.0F ///< Heat disinfect trimmer heater stop temperature in C. #define HEAT_DISINFECT_START_TEMP_AT_77_C 77.0F ///< Heat disinfect start disinfect at 77 C in C. #define HEAT_DISINFECT_STOP_TEMP_AT_76_C 76.0F ///< Heat disinfect stop disinfect at 76 C in C. #define HEAT_DISINFECT_START_TEMP_AT_82_C 82.0F ///< Heat disinfect start disinfect at 82 C in C. #define HEAT_DISINFECT_STOP_TEMP_AT_81_C 81.0F ///< Heat disinfect stop disinfect at 81 C in C. // R1 to R2 & R2 to R1 heat disinfect circulation #define HEAT_DISINFECT_TARGET_RO_FLOW_LPM 1.3F ///< Heat disinfect target RO flow rate in L/min. #define HEAT_DISINFECT_TARGET_RO_FLOW_TRANSFER_LPM 0.5F ///< Heat disinfect target RO flow rate in L/min when transferring between reservoirs. #define HEAT_DISINFECT_MAX_RO_PRESSURE_PSI 30 ///< Heat disinfect maximum RO pressure in psi. #define HEAT_DISINFECT_START_TEMP_TIMEOUT_MS ( 4 * MIN_PER_HOUR * SEC_PER_MIN * MS_PER_SECOND ) ///< Heat disinfect reaching to minimum temperature timeout in milliseconds. #define RSRVRS_TARGET_VOL_OUT_TIMEOUT_MS ( 10 * MS_PER_SECOND ) ///< Reservoirs 1 & 2 maximum volume out of range timeout during heat disinfect. #define RSRVRS_MAX_TARGET_VOL_CHANGE_ML 150.0F ///< Reservoirs 1 & 2 maximum allowed volume change when full during heat disinfect. #define POST_HEAT_DISINFECT_WAIT_TIME_MS ( 3 * SEC_PER_MIN * MS_PER_SECOND ) ///< Heat disinfect final wait time before flushing the system in milliseconds. #define HEAT_DISINFECT_MAX_TEMP_GRADIENT_C 15.0F ///< Heat disinfect maximum allowed temperature gradient in between hottest and coldest sensors. #define HEAT_DISINFECT_TEMP_GRAD_OUT_RANGE_TIME_MS ( 0.16 * SEC_PER_MIN * MS_PER_SECOND ) ///< Heat disinfect temperature gradient out of range timeout in milliseconds. #define HEAT_DIS_TEMP_GRAD_WAIT_2_CHECK_TIME_MS ( 5 * SEC_PER_MIN * MS_PER_SECOND ) #define HEAT_DISINFECT_TARGET_RO_PUMP_DC 0.4F ///< Heat disinfect target RO pump duty cycle. #define HEAT_DISINFECT_REF_RSRVR_TIMEOUT_MS ( 5 * MS_PER_SECOND ) ///< Heat disinfect getting reference reservoirs value timeout in milliseconds. #define HEAT_DISINFECT_AT_82_C_TIME_MS ( 10 * SEC_PER_MIN * MS_PER_SECOND ) ///< Heat disinfect time at 82 C in milliseconds. #define HEAT_DISINFECT_AT_77_C_TIME_MS ( 32 * SEC_PER_MIN * MS_PER_SECOND ) ///< Heat disinfect time at 77 C in milliseconds. #define HEAT_DISINFECT_PREP_FOR_TRANSFER_TIME_MS ( 30 * MS_PER_SECOND ) ///< Heat disinfect prepare for transfer hot water time in milliseconds. #define HEAT_DISNFECT_MIN_OVERRIDE_TIME_MS ( SEC_PER_MIN * MS_PER_SECOND ) ///< Heat disinfect minimum override time. // Mix drain R1 and R2 #define RSRVRS_MIX_DRAIN_TIMEOUT_MS ( 5 * SEC_PER_MIN * MS_PER_SECOND ) ///< Reservoirs 1 & 2 mix drain timeout in ms. #define DRAIN_PUMP_START_TIME_IN_MIX_DRAIN_MS ( 5 * MS_PER_SECOND ) ///< Time to start the drain pump at mix drain after directing the flow to drain in ms. #define DRAIN_PUMP_RPM_IN_MIX_DRAIN 600 ///< The RPM that the drain pump should be run during mix drain. #define MIX_DRAIN_WEIGHT_UNCHANGE_TIMEOUT ( 15 * MS_PER_SECOND ) ///< Time period of unchanged weight during mix draining before timeout. #define MIX_DRAIN_TEMPERATURE_THRESHOLD_C 60.0F ///< Temperature threshold for performing mix drain or normal drain. #ifndef _RELEASE_ #define NELSON_SUPPORT_TARGET_TEMP_C 6.0F // Nelson support heat disinfect target temperature in C. #define NELSON_SUPPORT_STOP_TEMP_C ( NELSON_SUPPORT_TARGET_TEMP_C - 5.0F ) // Nelson support heat disinfect stop temperature in C. #define NELSON_SUPPORT_DISINFECT_TIME_MS ( 2 * MIN_PER_HOUR * SEC_PER_MIN * MS_PER_SECOND ) // Nelson support heat disinfect time in milliseconds. #define NELSON_SUPPORT_INOCULATE_TIME_MS ( 30 * SEC_PER_MIN * MS_PER_SECOND ) // Nelson support inoculate time in milliseconds. #endif /// Heat disinfect status typedef enum Heat_disinfect_status { HEAT_DISINFECT_HEAT_UP_IN_PROGRESS = 0, ///< Heat disinfect in progress. HEAT_DISINFECT_DISINFECT_IN_PROGRESS, ///< Heat disinfect disinfect in progress. HEAT_DISINFECT_RSRVRS_LEAK_TIMEOUT, ///< Heat disinfect reservoirs leak timeout. HEAT_DISINFECT_HEAT_UP_TIMEOUT, ///< Heat disinfect heat up timeout. HEAT_DISINFECT_TEMP_GRADIENT_OUT_OF_RANGE, ///< Heat disinfect temperature gradient out of range. HEAT_DISINFECT_COMPLETE, ///< Heat disinfect complete. NUM_OF_HEAT_DISINFECT_STATUS ///< Number of heat disinfect status. } HEAT_DISINFECT_STATUS_T; /// Heat disinfect times enum typedef enum Heat_Disinfect_Times { RO_AT_77_C = 0, ///< Heat disinfect RO disinfect at 77 C. RO_AT_82_C, ///< Heat disinfect RO disinfect at 82 C. RSRVR_AT_77_C, ///< Heat disinfect reservoir disinfect at 77 C. RSRVR_AT_82_C, ///< Heat disinfect reservoir disinfect at 82 C. NUM_OF_HEAT_DISINFECT_TIMES ///< Number of heat disinfect times. } HEAT_DISINFECT_TIMES_T; /// Non-volatile write structure typedef struct { BOOL hasDisStatusBeenWrittenToNV; ///< Boolean flag to indicate whether the disinfect status been written to NV or not. } DISINFECT_NV_OPS_T; /// Heat disinfect time status structure typedef struct { U32 startTimeMS; ///< Heat disinfect start time in milliseconds. U32 targetTimeMS; ///< Heat disinfect target time in milliseconds. TEMPERATURE_SENSORS_T tempSensor; ///< Heat disinfect temperature sensor to check. F32 startTempC; ///< Heat disinfect temperature to start disinfect in C. F32 stopTempC; ///< Heat disinfect temperature to stop disinfect in C. } HEAT_DISINFECT_TIME_STATUS_T; // ********** private data ********** static DG_HEAT_DISINFECT_STATE_T heatDisinfectState; ///< Current active heat disinfect state. static DG_HEAT_DISINFECT_STATE_T prevHeatDisinfectState; ///< Previous active heat disinfect state before alarm. static DG_HEAT_DISINFECT_UI_STATE_T heatDisinfectUIState; ///< Current active heat disinfect UI state. static U32 overallHeatDisinfectTimer; ///< Heat disinfect cycle total timer. static U32 stateTimer; ///< Heat disinfect state timer to be used in different states. static U32 stateTrialCounter; ///< Heat disinfect state trial counter to be used for retries in different states. static BOOL areTempSensorsInRange; ///< Heat disinfect temperature sensors in/out range flag. static U32 concentratePumpsPrimeTimer; ///< Concentrate pumps prime timer. static DG_RESERVOIR_STATUS_T rsrvr1Status; ///< Reservoir 1 status. static DG_RESERVOIR_STATUS_T rsrvr2Status; ///< Reservoir 2 status. static F32 rsrvr1RefVolML; ///< Reservoir 1 reference volume in heat disinfect in milliliters. static F32 rsrvr2RefVolML; ///< Reservoir 2 reference volume in heat disinfect in milliliters. static U32 rsrvrsVolMonitorTimer; ///< Reservoir 1 & 2 volume monitor timers during heat disinfect. static BOOL areRsrvrsLeaking; ///< Reservoir 1 & 2 leak check flag during heat disinfect. static U32 dataPublishCounter; ///< Heat Disinfect data publish counter. static CANCELLATION_MODE_T cancellationMode; ///< Cancellation mode. static U32 rsrvrFillStableTimeCounter; ///< Reservoirs fill stable time counter. static ALARM_ID_T alarmDetectedPendingTrigger; ///< Heat disinfect alarm to raise. static BOOL isDrainPumpInMixDrainOn; ///< Flag to indicate the drain pump is on during mix drain. static U32 targetDisinfectTime; ///< Target disinfect time. static BOOL haveDrainParamsBeenInit[ NUM_OF_DG_RESERVOIRS ]; ///< Boolean flag to indicate whether the drain parameters have been reset or not. static U32 tempGradOutOfRangeTimer; ///< Temperature gradient out of range start timer. static DISINFECT_NV_OPS_T disinfectNVOps; ///< Disinfect non-volatile memory operations. static HEAT_DISINFECT_TIME_STATUS_T timeStatus[ NUM_OF_HEAT_DISINFECT_TIMES ]; ///< Heat disinfect time status. static F32 concPumpsStartTemperatureC; ///< Heat disinfect concentrate pumps start temperature in C. static BOOL isRODisinfectDone; ///< Heat disinfect is RO disinfect done flag. static OVERRIDE_U32_T targetTimer77C = { HEAT_DISINFECT_AT_77_C_TIME_MS, 0, 0, 0 }; ///< Heat disinfection override target timer at 77 C static OVERRIDE_U32_T targetTimer82C = { HEAT_DISINFECT_AT_82_C_TIME_MS, 0, 0, 0 }; ///< Heat disinfection override target timer at 82 C #ifndef _RELEASE_ /* Nelson Labs is in charge of testing the efficacy of the disinfects (heat and chem). The codes that contain the name Nelson are used to * support the special conditions that are needed to be created to test the disinfects. The support codes are not compiled in a release * build. */ static NELSON_SUPPORT_T nelsonSupport; // Nelson support. #endif // ********** private function prototypes ********** static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectStartState( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectDrainR1State( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectDrainR2State( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushDrainState( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushCirculationState( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushR1AndR2State( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushR2AndDrainR1State( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushDrainR2State( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushDrainR1State( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFillWithWaterState( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectDisinfectR1ToR2State( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectPrepareForHotWaterTransitionState( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFillR2WithHotWaterState( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectDisinfectR2ToR1State( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCoolDownHeatersState( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectMixDrainR1State( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectMixDrainR2State( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCancelModeBasicPathState( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCancelModeWaterPathState( void ); static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCompleteState( void ); static void failHeatDisinfect( void ); static DG_RESERVOIR_STATUS_T getRsrvrFillStatus( DG_RESERVOIR_ID_T reservoir, F32 targetVol, U32 timeout ); static DG_RESERVOIR_STATUS_T getRsrvrDrainStatus( DG_RESERVOIR_ID_T r, U32 drainSteadyStateTimeout, U32 timeout ); static HEAT_DISINFECT_STATUS_T getHeatDisinfectStatus( void ); static BOOL hasDisinfectTimeElapsed( HEAT_DISINFECT_TIMES_T disinfectTime ); static void publishHeatDisinfectData( void ); static void monitorModeHeatDisinfect( void ); static void writeDisinfectDataToNV( DG_USAGE_INFO_ITEMS_T info ); #ifndef _RELEASE_ static void setNelsonSupportConditions( void ); static DG_HEAT_DISINFECT_STATE_T handleNelsonHeatDisinfectFillR1WithWaterState( void ); #endif /*********************************************************************//** * @brief * The initHeatDisinfectMode function initializes the heat disinfect mode * module. * @details Inputs: none * @details Outputs: heatDisinfectState, stateTimer, isRODisinfectDone * stateTrialCounter, areTempSensorsInRange, rsrvr1Status, rsrvr2Status, * rsrvr1RefVolML, rsrvr2RefVolML, overallHeatDisinfectTimer, * cancellationMode, rsrvrFillStableTimeCounter, prevHeatDisinfectState * isPartialDisinfectInProgress, isDrainPumpOnInMixDrain, heatDisinfectTimer * hasROFCirculationBeenStarted, ROFCirculationTimer, targetDisinfectTime * ROFCirculationCoolingCounter, concentratePumpsPrimeTimer, areRsrvrsLeaking * haveDrainParamsBeenInit, tempGradOutOfRangeTimer, disinfectNVOps, * dataPublishCounter, timeStatus, concPumpsStartTemperatureC * @return none *************************************************************************/ void initHeatDisinfectMode( void ) { heatDisinfectState = DG_HEAT_DISINFECT_STATE_START; prevHeatDisinfectState = DG_HEAT_DISINFECT_STATE_START; heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_NOT_RUNNING; stateTimer = getMSTimerCount(); stateTrialCounter = 0; areTempSensorsInRange = FALSE; rsrvr1Status = NUM_OF_DG_RESERVOIR_STATUS; rsrvr2Status = NUM_OF_DG_RESERVOIR_STATUS; rsrvr1RefVolML = 0.0F; rsrvr2RefVolML = 0.0F; overallHeatDisinfectTimer = getMSTimerCount(); cancellationMode = CANCELLATION_MODE_NONE; rsrvrFillStableTimeCounter = 0; isDrainPumpInMixDrainOn = FALSE; concentratePumpsPrimeTimer = 0; targetDisinfectTime = 0; haveDrainParamsBeenInit[ DG_RESERVOIR_1 ] = FALSE; haveDrainParamsBeenInit[ DG_RESERVOIR_2 ] = FALSE; tempGradOutOfRangeTimer = 0; disinfectNVOps.hasDisStatusBeenWrittenToNV = FALSE; alarmDetectedPendingTrigger = ALARM_ID_NO_ALARM; rsrvrsVolMonitorTimer = 0; areRsrvrsLeaking = FALSE; dataPublishCounter = 0; concPumpsStartTemperatureC = HEAT_DISINFECT_CONC_PUMPS_START_TEMP_C; isRODisinfectDone = FALSE; // Initialize the disinfect times timeStatus[ RO_AT_77_C ].startTempC = HEAT_DISINFECT_START_TEMP_AT_77_C; timeStatus[ RO_AT_77_C ].startTimeMS = 0; timeStatus[ RO_AT_77_C ].stopTempC = HEAT_DISINFECT_STOP_TEMP_AT_76_C; timeStatus[ RO_AT_77_C ].targetTimeMS = getU32OverrideValue( &targetTimer77C ); timeStatus[ RO_AT_77_C ].tempSensor = TEMPSENSORS_HEAT_DISINFECT; timeStatus[ RO_AT_82_C ].startTempC = HEAT_DISINFECT_START_TEMP_AT_82_C; timeStatus[ RO_AT_82_C ].startTimeMS = 0; timeStatus[ RO_AT_82_C ].stopTempC = HEAT_DISINFECT_STOP_TEMP_AT_81_C; timeStatus[ RO_AT_82_C ].targetTimeMS = getU32OverrideValue( &targetTimer82C ); timeStatus[ RO_AT_82_C ].tempSensor = TEMPSENSORS_HEAT_DISINFECT; timeStatus[ RSRVR_AT_77_C ].startTempC = HEAT_DISINFECT_START_TEMP_AT_77_C; timeStatus[ RSRVR_AT_77_C ].startTimeMS = 0; timeStatus[ RSRVR_AT_77_C ].stopTempC = HEAT_DISINFECT_STOP_TEMP_AT_76_C; timeStatus[ RSRVR_AT_77_C ].targetTimeMS = getU32OverrideValue( &targetTimer77C ); timeStatus[ RSRVR_AT_77_C ].tempSensor = TEMPSENSORS_INLET_DIALYSATE; timeStatus[ RSRVR_AT_82_C ].startTempC = HEAT_DISINFECT_START_TEMP_AT_82_C; timeStatus[ RSRVR_AT_82_C ].startTimeMS = 0; timeStatus[ RSRVR_AT_82_C ].stopTempC = HEAT_DISINFECT_STOP_TEMP_AT_81_C; timeStatus[ RSRVR_AT_82_C ].targetTimeMS = getU32OverrideValue( &targetTimer82C ); timeStatus[ RSRVR_AT_82_C ].tempSensor = TEMPSENSORS_INLET_DIALYSATE; #ifndef _RELEASE_ setNelsonSupportConditions(); #endif } /*********************************************************************//** * @brief * The transitionToHeatDisinfectMode function prepares for transition to * heat disinfect mode. * @details Inputs: none * @details Outputs: none * @return initial state *************************************************************************/ U32 transitionToHeatDisinfectMode( void ) { // Set all the actuators to reset and de-energized state deenergizeActuators( NO_PARK_CONC_PUMPS ); initHeatDisinfectMode(); setCurrentSubState( NO_SUB_STATE ); setCPLDCleanLEDColor( CPLD_CLEAN_LED_ORANGE ); return heatDisinfectState; } /*********************************************************************//** * @brief * The isHeatDisinfectInTransitionHotWater function returns the status of * heat disinfect that whether it is in transition hot water water mode or * not * @details Inputs: heatDisinfectState * @details Outputs: none * @return TRUE if the mode is transfer hot water mode otherwise, FALSE *************************************************************************/ BOOL isHeatDisinfectInTransitionHotWater( void ) { BOOL status = FALSE; if ( ( DG_HEAT_DISINFECT_STATE_PREPARE_FOR_HOT_WATER_TRANSITION == heatDisinfectState ) || ( DG_HEAT_DISINFECT_STATE_FILL_R2_WITH_HOT_WATER == heatDisinfectState ) ) { status = TRUE; } return status; } /*********************************************************************//** * @brief * The execHeatDisinfectMode function executes the heat disinfect mode * state machine. * @details Inputs: heatDisinfectState * @details Outputs: heatDisinfectState * @return current state *************************************************************************/ U32 execHeatDisinfectMode( void ) { // The inlet pressure shall be checked all the time as long as VPi is open checkInletWaterPressure(); if ( heatDisinfectState != DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN ) { // Do not check on the inlet water temperature and conductivity until the inlet filters have been flushed // The initial states are drain reservoirs but in those states VPi is closed so these alarms are not checked checkInletWaterTemperature(); checkInletWaterConductivity(); } monitorModeHeatDisinfect(); switch ( heatDisinfectState ) { case DG_HEAT_DISINFECT_STATE_START: heatDisinfectState = handleHeatDisinfectStartState(); break; case DG_HEAT_DISINFECT_STATE_DRAIN_R1: heatDisinfectState = handleHeatDisinfectDrainR1State(); break; case DG_HEAT_DISINFECT_STATE_DRAIN_R2: heatDisinfectState = handleHeatDisinfectDrainR2State(); break; case DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN: heatDisinfectState = handleHeatDisinfectFlushDrainState(); break; case DG_HEAT_DISINFECT_STATE_FLUSH_CIRCULATION: heatDisinfectState = handleHeatDisinfectFlushCirculationState(); break; case DG_HEAT_DISINFECT_STATE_FLUSH_R1_AND_R2: heatDisinfectState = handleHeatDisinfectFlushR1AndR2State(); break; case DG_HEAT_DISINFECT_STATE_FLUSH_R2_AND_DRAIN_R1: heatDisinfectState = handleHeatDisinfectFlushR2AndDrainR1State(); break; case DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN_R2: heatDisinfectState = handleHeatDisinfectFlushDrainR2State(); break; case DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN_R1: heatDisinfectState = handleHeatDisinfectFlushDrainR1State(); break; case DG_HEAT_DISINFECT_STATE_FILL_WITH_WATER: heatDisinfectState = handleHeatDisinfectFillWithWaterState(); break; case DG_HEAT_DISINFECT_STATE_DISINFECT_R1_TO_R2: heatDisinfectState = handleHeatDisinfectDisinfectR1ToR2State(); break; case DG_HEAT_DISINFECT_STATE_PREPARE_FOR_HOT_WATER_TRANSITION: heatDisinfectState = handleHeatDisinfectPrepareForHotWaterTransitionState(); break; case DG_HEAT_DISINFECT_STATE_FILL_R2_WITH_HOT_WATER: heatDisinfectState = handleHeatDisinfectFillR2WithHotWaterState(); break; case DG_HEAT_DISINFECT_STATE_DISINFECT_R2_TO_R1: heatDisinfectState = handleHeatDisinfectDisinfectR2ToR1State(); break; case DG_HEAT_DISINFECT_STATE_COOL_DOWN_HEATERS: heatDisinfectState = handleHeatDisinfectCoolDownHeatersState(); break; case DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R1: heatDisinfectState = handleHeatDisinfectMixDrainR1State(); break; case DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R2: heatDisinfectState = handleHeatDisinfectMixDrainR2State(); break; case DG_HEAT_DISINFECT_STATE_CANCEL_BASIC_PATH: heatDisinfectState = handleHeatDisinfectCancelModeBasicPathState(); break; case DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH: heatDisinfectState = handleHeatDisinfectCancelModeWaterPathState(); break; case DG_HEAT_DISINFECT_STATE_COMPLETE: heatDisinfectState = handleHeatDisinfectCompleteState(); break; #ifndef _RELEASE_ case DG_NELSON_HEAT_DISINFECT_STATE_FILL_R1_WITH_WATER: heatDisinfectState = handleNelsonHeatDisinfectFillR1WithWaterState(); break; #endif default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_HEAT_DISINFECT_INVALID_EXEC_STATE, heatDisinfectState ) heatDisinfectState = DG_HEAT_DISINFECT_STATE_START; break; } publishHeatDisinfectData(); return heatDisinfectState; } /*********************************************************************//** * @brief * The getCurrentHeatDisinfectState function returns the current state of * the heat disinfect mode. * @details Inputs: heatDisinfectState * @details Outputs: none * @return the current state of heat disinfect mode. *************************************************************************/ DG_HEAT_DISINFECT_STATE_T getCurrentHeatDisinfectState( void ) { return heatDisinfectState; } /*********************************************************************//** * @brief * The stopDGHeatDisinfect function stops heat disinfect mode. * @details Inputs: none * @details Outputs: none * @return TRUE is current operation mode is heat disinfect, otherwise FALSE *************************************************************************/ BOOL stopDGHeatDisinfect( void ) { BOOL status = FALSE; // Check if the current operation mode is heat disinfect if ( DG_MODE_HEAT == getCurrentOperationMode() ) { // Reset all the actuators deenergizeActuators( NO_PARK_CONC_PUMPS ); // Transition to mode standby requestNewOperationMode( DG_MODE_STAN ); status = TRUE; } return status; } // ********** private functions ********** /*********************************************************************//** * @brief * The handleHeatDisinfectStartState function handles the heat disinfect * start state. The state checks the inlet pressure and the difference in * between TDi and TRo sensors and if they are not in range, it transitions * to basic cancellation path. Otherwise, it transitions to the next * state. * @details Inputs: alarm, rsrvrFillStableTimeCounter, rsrvr1Status, * stateTimer * @details Outputs: alarm, rsrvrFillStableTimeCounter, rsrvr1Status, * stateTimer * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectStartState( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_DRAIN_R1; overallHeatDisinfectTimer = getMSTimerCount(); heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_FLUSH_BEFORE_DISINFECT; rsrvrFillStableTimeCounter = 0; rsrvr1Status = DG_RESERVOIR_ABOVE_TARGET; stateTimer = getMSTimerCount(); // Close VPi to prevent wasting water setValveState( VPI, VALVE_STATE_CLOSED ); setValveState( VPO, VALVE_STATE_FILL_C_TO_NC); setValveState( VRD1, VALVE_STATE_OPEN ); setDrainPumpTargetRPM( DRAIN_PUMP_TARGET_RPM ); return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectDrainR1State function handles the heat disinfect * drain R1 state. The state drains reservoir 1. If the transition is * finished within the time, it transitions to the next state, otherwise, * it transitions to basic cancellation path. * @details Inputs: stateTimer, rsrvr1Status, rsrvr2Status * @details Outputs: stateTimer, rsrvr1Status, rsrvr2Status * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectDrainR1State( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_DRAIN_R1; if ( DG_RESERVOIR_ABOVE_TARGET == rsrvr1Status ) { rsrvr1Status = getRsrvrDrainStatus( DG_RESERVOIR_1, DRAIN_WEIGHT_UNCHANGE_TIMEOUT_MS, RSRVRS_INITIAL_DRAIN_TIMEOUT_MS ); } else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) { tareLoadCell( LOAD_CELL_RESERVOIR_1_PRIMARY ); tareLoadCell( LOAD_CELL_RESERVOIR_1_BACKUP ); // Done with draining R1, close it setValveState( VRD1, VALVE_STATE_CLOSED ); // Set the actuators to drain R2. // NOTE: Drain pump is already on and VDr is already on drain state setValveState( VRD2, VALVE_STATE_OPEN ); setValveState( VPO, VALVE_STATE_FILL_C_TO_NC); // Assume reservoir 2 is full and drain it rsrvr2Status = DG_RESERVOIR_ABOVE_TARGET; state = DG_HEAT_DISINFECT_STATE_DRAIN_R2; stateTimer = getMSTimerCount(); } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectDrainR2State function handles the heat disinfect * drain R2 state. The state drains reservoir 2. If the transition is * finished within the time, it transitions to the next state, otherwise, * it transitions to basic cancellation path. * @details Inputs: stateTimer, rsrvr2Status * stateTrialCounter * @details Outputs: stateTimer, rsrvr2Status, stateTrialCounter * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectDrainR2State( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_DRAIN_R2; if ( DG_RESERVOIR_ABOVE_TARGET == rsrvr2Status ) { rsrvr2Status = getRsrvrDrainStatus( DG_RESERVOIR_2, DRAIN_WEIGHT_UNCHANGE_TIMEOUT_MS, RSRVRS_INITIAL_DRAIN_TIMEOUT_MS ); } else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) { tareLoadCell( LOAD_CELL_RESERVOIR_2_PRIMARY ); tareLoadCell( LOAD_CELL_RESERVOIR_2_BACKUP ); signalDrainPumpHardStop(); #ifndef _RELEASE_ if ( NELSON_DRAIN_SAMPLES == nelsonSupport ) { state = DG_HEAT_DISINFECT_STATE_COMPLETE; } else #endif { // Done with draining R2, close it turnOnUVReactor( INLET_UV_REACTOR ); setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); setValveState( VRD2, VALVE_STATE_CLOSED ); setValveState( VPI, VALVE_STATE_OPEN ); stateTrialCounter = 0; stateTimer = getMSTimerCount(); state = DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN; } } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr2Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectFlushDrainState function handles the heat disinfect * flush drain state. The state flushes the drain line for a period of time * and then measures the temperature and conductivity of water. If they are * not within the range, it transitions to basic cancellation path, otherwise * it transitions to the next state. * @details Inputs: stateTimer, stateTrialCounter, alarm, * prevHeatDisinfectState * @details Outputs: stateTimer, stateTrialCounter, alarm, * prevHeatDisinfectState * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushDrainState( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN; // Check if flush time has elapsed if ( TRUE == didTimeout( stateTimer, FLUSH_DRAIN_WAIT_TIME_MS ) ) { setValveState( VPD, VALVE_STATE_OPEN_C_TO_NC ); setROPumpTargetFlowRateLPM( RO_PUMP_TARGET_FLUSH_FILL_FLOW_RATE_LPM, MAX_RO_PUMP_FLUSH_FILL_PRESSURE_PSI ); stateTimer = getMSTimerCount(); stateTrialCounter = 0; state = DG_HEAT_DISINFECT_STATE_FLUSH_CIRCULATION; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectFlushCirculationState function handles the heat * disinfect flush circulation state. The state flushes the circulation * line for a period of time. After the flush if the temperature sensors * are not within a certain degrees from each other, the state transitions * to basic cancellation path, otherwise, it transitions to the next state. * @details Inputs: stateTimer, stateTrialCounter, prevHeatDisinfectState * alarm, areTempSensorsInRange, rsrvr1Status, rsrvr2Status * @details Outputs: stateTimer, stateTrialCounter, prevHeatDisinfectState, * alarm, areTempSensorsInRange, rsrvr1Status, rsrvr2Status * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushCirculationState( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_FLUSH_CIRCULATION; writeDisinfectDataToNV( USAGE_INFO_FILTER_FLUSH ); // Check if the flush circulation time has elapsed and the temperature sensors are not in range yet if ( ( TRUE == didTimeout( stateTimer, FLUSH_CICRCULATION_WAIT_TIME_MS ) ) && ( FALSE == areTempSensorsInRange ) ) { F32 ThdTemp = getTemperatureValue( TEMPSENSORS_HEAT_DISINFECT ); F32 TPoTemp = getTemperatureValue( TEMPSENSORS_OUTLET_PRIMARY_HEATER ); F32 TD2Temp = getTemperatureValue( TEMPSENSORS_CONDUCTIVITY_SENSOR_2 ); F32 avgTemp = ( ThdTemp + TPoTemp + TD2Temp ) / NUM_OF_TEMP_SENSORS_TO_AVG; // Check if any of the temperature sensors deviate for more than the defined value from the average of all // of the temperature sensors BOOL isThdOut = ( fabs( ThdTemp - avgTemp ) > MAX_FLUSH_CIRC_TEMP_SENSOR_DIFF_C ? TRUE : FALSE ); BOOL isTPoOut = ( fabs( TPoTemp - avgTemp ) > MAX_FLUSH_CIRC_TEMP_SENSOR_DIFF_C ? TRUE : FALSE ); BOOL isTD2Out = ( fabs( TD2Temp - avgTemp ) > MAX_FLUSH_CIRC_TEMP_SENSOR_DIFF_C ? TRUE : FALSE ); // Check if any of the temperature sensors are out of tolerance if ( ( TRUE == isThdOut ) || ( TRUE == isTPoOut ) || ( TRUE == isTD2Out ) ) { // Check if we have exceeded the number of trials. If not, try another time if ( stateTrialCounter < MAX_ALLOWED_STATE_TRIALS ) { stateTrialCounter += 1; stateTimer = getMSTimerCount(); } // State failed. Cancel heat disinfect mode else { alarmDetectedPendingTrigger = ALARM_ID_DG_CLEANING_MODE_TEMP_SENSORS_DIFF_OUT_OF_RANGE; prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } } else { areTempSensorsInRange = TRUE; // Turn the pumps on in reverse setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP1_ACID, ACID_PUMP_FLUSH_RECIRCULATE_SPEED_MLPM ); setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP2_BICARB, BICARB_PUMP_FLUSH_RECIRCULATE_SPEED_MLPM ); // Turn on the concentrate pumps requestConcentratePumpOn( CONCENTRATEPUMPS_CP1_ACID ); requestConcentratePumpOn( CONCENTRATEPUMPS_CP2_BICARB ); concentratePumpsPrimeTimer = getMSTimerCount(); } } // Only start the concentrate pumps if the temperature sensors are in range if ( TRUE == areTempSensorsInRange ) { if ( TRUE == didTimeout( concentratePumpsPrimeTimer, FLUSH_CIRCULATION_CONC_PUMPS_WAIT_TIME_MS ) ) { rsrvr1Status = DG_RESERVOIR_BELOW_TARGET; rsrvr2Status = DG_RESERVOIR_BELOW_TARGET; // Done with flushing the concentrate pumps line requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID, NO_PARK_CONC_PUMPS ); requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB, NO_PARK_CONC_PUMPS ); setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); #ifndef _RELEASE_ if ( nelsonSupport != NELSON_INOCULATE ) #endif { setHeaterTargetTemperature( DG_PRIMARY_HEATER, HEAT_DISINFECT_PRIM_HEATER_FLUSH_TARGET_TEMP_C ); startHeater( DG_PRIMARY_HEATER ); } // Set the NV data to FLASE to be able to write another NV data ops disinfectNVOps.hasDisStatusBeenWrittenToNV = FALSE; stateTimer = getMSTimerCount(); state = DG_HEAT_DISINFECT_STATE_FLUSH_R1_AND_R2; } } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectFlushR1AndR2State function handles the heat * disinfect flush reservoir 1 and reservoir 2 state. If the reservoirs * did not flush within a period time, the state transitions to water * cancellation path. * @details Inputs: stateTimer, rsrvr1Status, rsrvr2Status, * prevHeatDisinfectState * @details Outputs: stateTimer, rsrvr1Status, rsrvr2Status, * prevHeatDisinfectState * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushR1AndR2State( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_FLUSH_R1_AND_R2; // If R1 is not full, keep monitoring for R1 level and timeout if ( DG_RESERVOIR_BELOW_TARGET == rsrvr1Status ) { F32 volumML = getLoadCellSmallFilteredWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); rsrvr1Status = getRsrvrFillStatus( DG_RESERVOIR_1, RSRVRS_FULL_VOL_ML, RSRVRS_FILL_UP_TIMEOUT_MS ); // Reservoir 2 cannot be filled before reservoir 1 is filled and is overflowing to reservoir 2. If reservoir 2 has already // reached to target volume, it means reservoir 1's load cell might be reading incorrect values. This situation might continue // until reservoir 2 is filled up and the tubing might expand or leak. if ( volumML >= RSRVRS_PARTIAL_FILL_VOL_ML ) { prevHeatDisinfectState = state; alarmDetectedPendingTrigger = ALARM_ID_DG_INVALID_LOAD_CELL_VALUE; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } } // Once R1 is full, keep monitoring for R2 level and timeout else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) { rsrvr2Status = getRsrvrFillStatus( DG_RESERVOIR_2, RSRVRS_PARTIAL_FILL_VOL_ML, RSRVRS_500ML_FILL_UP_TIMEOUT_MS ); // Once R2 is full (to 500mL in this case), transition to the next state if ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) { // Set the actuators to flush R2 and drain R1 state setValveState( VRF, VALVE_STATE_R2_C_TO_NO ); setValveState( VRI, VALVE_STATE_R1_C_TO_NO ); setValveState( VRO, VALVE_STATE_R2_C_TO_NC ); setValveState( VRD1, VALVE_STATE_OPEN ); setDrainPumpTargetRPM( DRAIN_PUMP_TARGET_RPM ); stateTimer = getMSTimerCount(); // Set both reservoirs status rsrvr1Status = DG_RESERVOIR_ABOVE_TARGET; rsrvr2Status = DG_RESERVOIR_BELOW_TARGET; state = DG_HEAT_DISINFECT_STATE_FLUSH_R2_AND_DRAIN_R1; } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr2Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectFlushR2AndDrainR1State function handles the heat * disinfect flush reservoir 2 and drain reservoir 1 state. The state * drains reservoir 1 and flushes reservoir 2 at the same time until the * water in reservoir 2 overflows to reservoir 1. If this process is done * within a certain period of time, it transitions to the next state. * If the drain process times out, it transitions to basic cancellation and * if the flush process times out, it transitions to water cancellation. * @details Inputs: stateTimer, rsrvr1Status, rsrvr2Status * @details Outputs: stateTimer, rsrvr1Status, rsrvr2Status * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushR2AndDrainR1State( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_FLUSH_R2_AND_DRAIN_R1; // If reservoir 1 is empty, turn off the drain pump if ( DG_RESERVOIR_ABOVE_TARGET == rsrvr1Status ) { rsrvr1Status = getRsrvrDrainStatus( DG_RESERVOIR_1, DRAIN_WEIGHT_UNCHANGE_TIMEOUT_MS, RSRVRS_INITIAL_DRAIN_TIMEOUT_MS ); } else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) { // Done with draining R1 signalDrainPumpHardStop(); // Close VRD1 setValveState( VRD1, VALVE_STATE_CLOSED ); } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } // First reservoir 2 must be completely full if ( DG_RESERVOIR_BELOW_TARGET == rsrvr2Status ) { rsrvr2Status = getRsrvrFillStatus( DG_RESERVOIR_2, RSRVRS_FULL_VOL_ML, RSRVRS_FILL_UP_TIMEOUT_MS ); U32 drainPumpRPM = getDrainPumpTargetRPM(); // Keep monitoring the status of reservoir 1 as the same time F32 volume = getLoadCellSmallFilteredWeight( LOAD_CELL_RESERVOIR_1_PRIMARY ); // Reservoir 1 cannot be filled before reservoir 2 is filled and is overflowing to reservoir 1. If reservoir 1 has already // reached to target volume, it means reservoir 2's load cell might be reading incorrect values. This situation might continue // until reservoir 1 is filled up and the tubing might expand or leak. // Before checking whether reservoir 1 is filled pre-maturely, we have to make sure reservoir 1 is drained completely to make // sure the extra volume that is read is not because of previous water that is being drained currently and it is above 500 mL if ( ( volume >= RSRVRS_PARTIAL_FILL_VOL_ML ) && ( 0 == drainPumpRPM ) ) { prevHeatDisinfectState = state; alarmDetectedPendingTrigger = ALARM_ID_DG_INVALID_LOAD_CELL_VALUE; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } } // Once R2 is full, R1 must be partially full else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) { rsrvr1Status = getRsrvrFillStatus( DG_RESERVOIR_1, RSRVRS_PARTIAL_FILL_VOL_ML, RSRVRS_500ML_FILL_UP_TIMEOUT_MS ); // Once R1 is partially full, transition to the next state if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) { // Done with filing turn off the RO pump signalROPumpHardStop(); stopHeater( DG_PRIMARY_HEATER ); turnOffUVReactor( INLET_UV_REACTOR ); turnOffUVReactor( OUTLET_UV_REACTOR ); // Set the valves to drain R2 and no fill setValveState( VPI, VALVE_STATE_CLOSED ); setValveState( VPD, VALVE_STATE_DRAIN_C_TO_NO ); setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRD2, VALVE_STATE_OPEN ); setDrainPumpTargetRPM( DRAIN_PUMP_TARGET_RPM ); // Start the timer for drain timeout stateTimer = getMSTimerCount(); rsrvr2Status = DG_RESERVOIR_ABOVE_TARGET; state = DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN_R2; } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr2Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectFlushDrainR2State function handles the heat * disinfect flush drain reservoir 2 state. The state drains reservoir 2 * and if the drain times out, it transitions to basic cancellation. If the * drain is finished within a certain period of time, it transitions to the * next state. * @details Inputs: stateTimer, rsrvr1Status, rsrvr2Status * @details Outputs: stateTimer, rsrvr1Status, rsrvr2Status * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushDrainR2State( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN_R2; // If reservoir 2 is empty, set the drain valve to drain R1 if ( DG_RESERVOIR_ABOVE_TARGET == rsrvr2Status ) { rsrvr2Status = getRsrvrDrainStatus( DG_RESERVOIR_2, DRAIN_WEIGHT_UNCHANGE_TIMEOUT_MS, RSRVRS_INITIAL_DRAIN_TIMEOUT_MS ); } else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) { setValveState( VRD1, VALVE_STATE_OPEN ); setValveState( VRD2, VALVE_STATE_CLOSED ); // Start the timer for drain timeout stateTimer = getMSTimerCount(); rsrvr1Status = DG_RESERVOIR_ABOVE_TARGET; state = DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN_R1; } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr2Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectFlushDrainR1State function handles the heat * disinfect flush drain reservoir 1 state. The state drains reservoir 1 * and if the drain times out, it transitions to basic cancellation. If the * drain is finished within a certain period of time, it transitions to the * next state. * @details Inputs: stateTimer, rsrvr1Status, rsrvr2Status * @details Outputs: stateTimer, rsrvr1Status, rsrvr2Status * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFlushDrainR1State( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN_R1; // If reservoir 1 is empty, set the state to fill water state if ( DG_RESERVOIR_ABOVE_TARGET == rsrvr1Status ) { rsrvr1Status = getRsrvrDrainStatus( DG_RESERVOIR_1, DRAIN_WEIGHT_UNCHANGE_TIMEOUT_MS, RSRVRS_INITIAL_DRAIN_TIMEOUT_MS ); } else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) { // Done with draining the reservoirs signalDrainPumpHardStop(); // Prepare for filling the reservoirs and heating the water setValveState( VPI, VALVE_STATE_OPEN ); setValveState( VPD, VALVE_STATE_OPEN_C_TO_NC ); setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); setValveState( VRD1, VALVE_STATE_CLOSED ); setValveState( VRD2, VALVE_STATE_CLOSED ); turnOnUVReactor( INLET_UV_REACTOR ); // Turn on the RO pump setROPumpTargetFlowRateLPM( RO_PUMP_TARGET_FLUSH_FILL_FLOW_RATE_LPM, MAX_RO_PUMP_FLUSH_FILL_PRESSURE_PSI ); #ifndef _RELEASE_ if ( nelsonSupport != NELSON_INOCULATE ) #endif { // Start heating the water while we are filling up the reservoirs setHeaterTargetTemperature( DG_PRIMARY_HEATER, HEAT_DISINFECT_PRIM_HEATER_TARGET_TEMP_C ); startHeater( DG_PRIMARY_HEATER ); } rsrvr1Status = DG_RESERVOIR_BELOW_TARGET; rsrvr2Status = DG_RESERVOIR_BELOW_TARGET; stateTimer = getMSTimerCount(); state = DG_HEAT_DISINFECT_STATE_FILL_WITH_WATER; } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectFillWithWaterState function handles the heat * disinfect fill with water state. The state fills reservoir 1 until it * overflows to reservoir 2. If the filling process times out, it * transitions to water cancellation state, otherwise, it transitions to * next state. The levels of the reservoirs are recorded to be monitored * during heat disinfect. * @details Inputs: stateTimer, rsrvr1Status, rsrvr2Status, R1HeatDisinfectVol * R2HeatDisinfectVol, rsrvrsVolMonitorTimer * @details Outputs: stateTimer, rsrvr1Status, rsrvr2Status R1HeatDisinfectVol * R2HeatDisinfectVol, rsrvrsVolMonitorTimer * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFillWithWaterState( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_FILL_WITH_WATER; heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_HEAT_UP_WATER; // First reservoir 1 must be full if ( DG_RESERVOIR_BELOW_TARGET == rsrvr1Status ) { rsrvr1Status = getRsrvrFillStatus( DG_RESERVOIR_1, RSRVRS_FULL_VOL_ML, RSRVRS_FILL_UP_TIMEOUT_MS ); } // Once reservoir 1 is full, check the status of reservoir 2 since the water overflows to reservoir 2 else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) { rsrvr2Status = getRsrvrFillStatus( DG_RESERVOIR_2, RSRVRS_PARTIAL_FILL_VOL_ML, RSRVRS_500ML_FILL_UP_TIMEOUT_MS ); // Once reservoir 2 is full, set the actuators for recirculation if ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) { turnOffUVReactor( INLET_UV_REACTOR ); setValveState( VPI, VALVE_STATE_CLOSED ); setValveState( VBF, VALVE_STATE_OPEN ); setValveState( VPD, VALVE_STATE_DRAIN_C_TO_NO ); setValveState( VDR, VALVE_STATE_RECIRC_C_TO_NC ); setValveState( VRC, VALVE_STATE_RECIRC_C_TO_NC ); setValveState( VRD1, VALVE_STATE_CLOSED ); setValveState( VRD2, VALVE_STATE_OPEN ); signalROPumpHardStop(); // Set the drain pump to control mode setDrainPumpTargetOutletFlowLPM( HEAT_DISINFECT_TARGET_RO_FLOW_LPM ); #ifndef _RELEASE_ if ( nelsonSupport != NELSON_INOCULATE ) #endif { setHeaterTargetTemperature( DG_TRIMMER_HEATER, HEAT_DISINFECT_TRIM_HEATER_TARGET_TEMP_C ); startHeater( DG_TRIMMER_HEATER ); } stateTimer = getMSTimerCount(); rsrvrsVolMonitorTimer = getMSTimerCount(); state = DG_HEAT_DISINFECT_STATE_DISINFECT_R1_TO_R2; } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr2Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectDisinfectR1ToR2State function handles the heat * disinfect R1 to R2 state. The state runs reservoir 1 to reservoir 2 * heat disinfect. If the reservoirs leak or it cannot reach to temperature * within a certain period of time, it transitions to water cancellation. * If heat disinfect reservoir 1 to reservoir 2 is completed, it transitions * to the next state. * @details Inputs: stateTimer, rsrvr1Status, rsrvr2Status * @details Outputs: stateTimer, rsrvr1Status, rsrvr2Status, tempGradOutOfRangeTimer * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectDisinfectR1ToR2State( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_DISINFECT_R1_TO_R2; HEAT_DISINFECT_STATUS_T status = getHeatDisinfectStatus(); if ( FALSE == isROPumpRunning() ) { setROPumpTargetDutyCycle( HEAT_DISINFECT_TARGET_RO_PUMP_DC ); } if ( TRUE == didTimeout( stateTimer, HEAT_DISINFECT_REF_RSRVR_TIMEOUT_MS ) ) { if ( ( rsrvr1RefVolML < NEARLY_ZERO ) && ( rsrvr2RefVolML < NEARLY_ZERO ) ) { // Get the current volumes of R1 & R2. These values will be used to make sure the reservoirs' // volume does not change more than a certain amount during the actual heat disinfect cycle rsrvr1RefVolML = getLoadCellLargeFilteredWeight( LOAD_CELL_RESERVOIR_1_PRIMARY ); rsrvr2RefVolML = getLoadCellLargeFilteredWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); } } switch ( status ) { case HEAT_DISINFECT_RSRVRS_LEAK_TIMEOUT: case HEAT_DISINFECT_HEAT_UP_TIMEOUT: case HEAT_DISINFECT_TEMP_GRADIENT_OUT_OF_RANGE: state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; break; case HEAT_DISINFECT_DISINFECT_IN_PROGRESS: heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_DISINFECT_RESERVOIR_1; break; case HEAT_DISINFECT_COMPLETE: requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID, NO_PARK_CONC_PUMPS ); requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB, NO_PARK_CONC_PUMPS ); // Although there is fluid in both reservoirs, but they are set to empty // to begin the transition of hot water from R1 to R2. rsrvr2Status = DG_RESERVOIR_BELOW_TARGET; rsrvr1Status = DG_RESERVOIR_BELOW_TARGET; stateTimer = getMSTimerCount(); rsrvr1RefVolML = 0.0F; rsrvr2RefVolML = 0.0F; tempGradOutOfRangeTimer = 0; state = DG_HEAT_DISINFECT_STATE_PREPARE_FOR_HOT_WATER_TRANSITION; timeStatus[ RSRVR_AT_77_C ].startTimeMS = 0; timeStatus[ RSRVR_AT_82_C ].startTimeMS = 0; break; case HEAT_DISINFECT_HEAT_UP_IN_PROGRESS: heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_HEAT_UP_WATER; break; #ifndef _VECTORCAST_ default: heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_HEAT_UP_WATER; break; #endif } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectPrepareForHotWaterTransitionState function handles * the heat disinfect prepare for hot water state. In this state, the fluid is * run a certain period of time with the heaters are at a much lower duty cycle * and then the actuators are set to transition the hot water to reservoir 2. * @details Inputs: stateTimer * @details Outputs: stateTimer * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectPrepareForHotWaterTransitionState( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_PREPARE_FOR_HOT_WATER_TRANSITION; if ( TRUE == didTimeout( stateTimer, HEAT_DISINFECT_PREP_FOR_TRANSFER_TIME_MS ) ) { // Set the valves to transfer hot water from R1 to R2 and fill up R2. setValveState( VRO, VALVE_STATE_R2_C_TO_NC ); setValveState( VRD1, VALVE_STATE_OPEN ); setValveState( VRD2, VALVE_STATE_CLOSED ); setValveState( VRI, VALVE_STATE_R1_C_TO_NO ); setValveState( VRF, VALVE_STATE_R2_C_TO_NO ); setDrainPumpTargetOutletFlowLPM( HEAT_DISINFECT_TARGET_RO_FLOW_TRANSFER_LPM ); // Turn off trimmer heater for transition stopHeater( DG_TRIMMER_HEATER ); stateTimer = getMSTimerCount(); state = DG_HEAT_DISINFECT_STATE_FILL_R2_WITH_HOT_WATER; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectFillR2WithHotWaterState function handles fill R2 * with water state. The state transfers hot water from reservoir 1 to * reservoir 2 until hot water overflows from reservoir 2 to reservoir 1. * If the fill times out, it transitions to water cancellation state, * otherwise, it transitions to the next state. * @details Inputs: rsrvr1Status, rsrvr2Status, R1HeatDisinfectVol, * R2HeatDisinfectVol * @details Outputs: rsrvr1Status, rsrvr2Status, R1HeatDisinfectVol, * R2HeatDisinfectVol * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectFillR2WithHotWaterState( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_FILL_R2_WITH_HOT_WATER; heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_TRANSITION_HOT_WATER; // First reservoir 1 must be partially full if ( DG_RESERVOIR_BELOW_TARGET == rsrvr1Status ) { rsrvr1Status = getRsrvrFillStatus( DG_RESERVOIR_1, RSRVRS_PARTIAL_FILL_VOL_ML, RSRVRS_500ML_FILL_UP_TIMEOUT_MS ); } else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) { rsrvr2Status = getRsrvrFillStatus( DG_RESERVOIR_2, RSRVRS_FULL_VOL_ML, RSRVRS_FILL_UP_TIMEOUT_MS ); if ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) { // Set the drain pump to control mode setDrainPumpTargetOutletFlowLPM( HEAT_DISINFECT_TARGET_RO_FLOW_LPM ); stateTimer = getMSTimerCount(); state = DG_HEAT_DISINFECT_STATE_DISINFECT_R2_TO_R1; } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr2Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectDisinfectR2ToR1State function handles the heat * disinfect R2 to R1 state. If the reservoirs leak or it cannot reach to * temperature within a certain period of time, it transitions to water * cancellation state. If heat disinfect reservoir 1 to reservoir 2 is * completed, it transitions to the next state. * @details Inputs: stateTimer, rsrvr1Status * @details Outputs: stateTimer, rsrvr1Status * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectDisinfectR2ToR1State( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_DISINFECT_R2_TO_R1; HEAT_DISINFECT_STATUS_T status = getHeatDisinfectStatus(); heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_DISINFECT_RESERVOIR_2; if ( TRUE == didTimeout( stateTimer, HEAT_DISINFECT_REF_RSRVR_TIMEOUT_MS ) ) { if ( ( rsrvr1RefVolML < NEARLY_ZERO ) && ( rsrvr2RefVolML < NEARLY_ZERO ) ) { // Get the current volumes of R1 & R2. These values will be used to make sure the reservoirs' // volume does not change more than a certain amount during the actual heat disinfect cycle rsrvr1RefVolML = getLoadCellLargeFilteredWeight( LOAD_CELL_RESERVOIR_1_PRIMARY ); rsrvr2RefVolML = getLoadCellLargeFilteredWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); } } switch ( status ) { case HEAT_DISINFECT_RSRVRS_LEAK_TIMEOUT: case HEAT_DISINFECT_HEAT_UP_TIMEOUT: case HEAT_DISINFECT_TEMP_GRADIENT_OUT_OF_RANGE: state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; break; case HEAT_DISINFECT_COMPLETE: if ( TRUE == isRODisinfectDone ) { #ifndef _RELEASE_ if ( ( NELSON_HEAT_DISINFECT == nelsonSupport ) || ( NELSON_POS_CONTROL_HEAT_DISINFECT == nelsonSupport ) ) { deenergizeActuators( NO_PARK_CONC_PUMPS ); state = DG_HEAT_DISINFECT_STATE_COMPLETE; } else #endif { requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID, NO_PARK_CONC_PUMPS ); requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB, NO_PARK_CONC_PUMPS ); stopHeater( DG_PRIMARY_HEATER ); stopHeater( DG_TRIMMER_HEATER ); timeStatus[ RSRVR_AT_77_C ].startTimeMS = 0; timeStatus[ RSRVR_AT_82_C ].startTimeMS = 0; timeStatus[ RO_AT_77_C ].startTimeMS = 0; timeStatus[ RO_AT_82_C ].startTimeMS = 0; rsrvr1RefVolML = 0.0F; rsrvr2RefVolML = 0.0F; stateTimer = getMSTimerCount(); state = DG_HEAT_DISINFECT_STATE_COOL_DOWN_HEATERS; } } #ifndef _RELEASE_ if ( NELSON_INOCULATE == nelsonSupport ) { // Set the valves to transfer hot water from R1 to R2 and fill up R2. setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRD1, VALVE_STATE_CLOSED ); setValveState( VRD2, VALVE_STATE_OPEN ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); setDrainPumpTargetOutletFlowLPM( HEAT_DISINFECT_TARGET_RO_FLOW_TRANSFER_LPM ); // Although there is fluid in both reservoirs, but they are set to empty // to begin the transition of hot water from R1 to R2. rsrvr2Status = DG_RESERVOIR_BELOW_TARGET; rsrvr1Status = DG_RESERVOIR_BELOW_TARGET; stateTimer = getMSTimerCount(); rsrvr1RefVolML = 0.0F; rsrvr2RefVolML = 0.0F; state = DG_NELSON_HEAT_DISINFECT_STATE_FILL_R1_WITH_WATER; } #endif break; case HEAT_DISINFECT_HEAT_UP_IN_PROGRESS: default: // Do nothing heat disinfect is in progress. break; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectCoolDownHeatersState function handles the heat * disinfect cool down heaters state. The state continues running the fluid * while the heaters are off for a certain period of time. * @details Inputs: stateTimer * @details Outputs: stateTimer, heatDisinfectUIState * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCoolDownHeatersState( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_COOL_DOWN_HEATERS; heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_COOL_DOWN_DEVICE; writeDisinfectDataToNV( USAGE_INFO_HEAT_DIS ); if ( TRUE == didTimeout( stateTimer, POST_HEAT_DISINFECT_WAIT_TIME_MS ) ) { signalROPumpHardStop(); if ( TRUE == isDrainPumpOn() ) { // Stop the drain pump and the RO pump to exit the closed loop signalDrainPumpHardStop(); setValveState( VRD1, VALVE_STATE_CLOSED ); } else { setValveState( VDR, VALVE_STATE_DRAIN_C_TO_NO ); setValveState( VRC, VALVE_STATE_DRAIN_C_TO_NO ); rsrvr1Status = DG_RESERVOIR_ABOVE_TARGET; stateTimer = getMSTimerCount(); state = DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R1; } } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectMixDrainR1State function handles the heat * disinfect mix drain R1 state. The state drains reservoir 1 and if it * times out, it transitions to basic cancellation state. Otherwise, it * transitions to the next state. * @details Inputs: stateTimer, rsrvr1Status, rsrvr2Status, * isDrainPumpOnInMixDrain * @details Outputs: stateTimer, rsrvr1Status, rsrvr2Status, * isDrainPumpOnInMixDrain * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectMixDrainR1State( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R1; heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_FLUSH_AFTER_DISINFECT; if ( ( TRUE == didTimeout( stateTimer, DRAIN_PUMP_START_TIME_IN_MIX_DRAIN_MS ) ) && ( FALSE == isDrainPumpInMixDrainOn ) ) { isDrainPumpInMixDrainOn = TRUE; setValveState( VPI, VALVE_STATE_OPEN ); setValveState( VRD1, VALVE_STATE_OPEN ); setValveState( VBF, VALVE_STATE_CLOSED ); setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); setValveState( VDR, VALVE_STATE_DRAIN_C_TO_NO ); setValveState( VRC, VALVE_STATE_DRAIN_C_TO_NO ); setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); // Turn on the drain pump to drain the reservoirs in open loop mode setDrainPumpTargetRPM( DRAIN_PUMP_RPM_IN_MIX_DRAIN ); turnOnUVReactor( INLET_UV_REACTOR ); } else if ( TRUE == isDrainPumpInMixDrainOn ) { if ( DG_RESERVOIR_ABOVE_TARGET == rsrvr1Status ) { rsrvr1Status = getRsrvrDrainStatus( DG_RESERVOIR_1, MIX_DRAIN_WEIGHT_UNCHANGE_TIMEOUT, RSRVRS_MIX_DRAIN_TIMEOUT_MS ); } else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) { // Done with draining reservoir 1 setValveState( VRD1, VALVE_STATE_CLOSED ); // Set the drain valve to reservoir 2 setValveState( VRD2, VALVE_STATE_OPEN ); rsrvr2Status = DG_RESERVOIR_ABOVE_TARGET; stateTimer = getMSTimerCount(); state = DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R2; } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectMixDrainR2State function handles the heat * disinfect mix drain R2 state. The state drains reservoir 1 and if it * times out, it transitions to basic cancellation state. Otherwise, it * transitions to the next state. * @details Inputs: stateTimer, rsrvr1Status, rsrvr2Status * @details Outputs: stateTimer, rsrvr1Status, rsrvr2Status * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectMixDrainR2State( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R2; if ( DG_RESERVOIR_ABOVE_TARGET == rsrvr2Status ) { rsrvr2Status = getRsrvrDrainStatus( DG_RESERVOIR_2, MIX_DRAIN_WEIGHT_UNCHANGE_TIMEOUT, RSRVRS_MIX_DRAIN_TIMEOUT_MS ); } else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) { state = DG_HEAT_DISINFECT_STATE_COMPLETE; } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr2Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectCancelModeBasicPathState function handles the * heat disinfect cancel mode basic path state. The state sets the state * to complete and raises an alarm. * @details Inputs: cancellationMode * @details Outputs: cancellationMode * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCancelModeBasicPathState( void ) { // Go to state complete, we are done DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_CANCEL_BASIC_PATH; // Set the heat disinfect UI state heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_CANCEL_DISINFECT; cancellationMode = CANCELLATION_MODE_BASIC; failHeatDisinfect(); return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectCancelModeWaterPathState function handles the * heat disinfect cancel mode cold water path state. The state resets all * the actuators. If THd > 60 C, it runs a hot water cancellation to make sure * the water that is drained is below 60 C. Otherwise, it runs a cold water * drain. If the drain times out, it transitions to basic cancellation state. * If the drain is completed within the define time, it transitions to the * complete state. * @details Inputs: rsrvr1Status, rsrvr2Status, cancellationMode, stateTimer * @details Outputs: rsrvr1Status, rsrvr2Status, cancellationMode, stateTimer, * haveDrainParamsBeenInit * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCancelModeWaterPathState( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; U32 drainTimeoutMS = ( CANCELLATION_MODE_COLD == cancellationMode ? RSRVRS_INITIAL_DRAIN_TIMEOUT_MS : RSRVRS_MIX_DRAIN_TIMEOUT_MS ); // Set the heat disinfect UI state heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_CANCEL_DISINFECT; if ( CANCELLATION_MODE_NONE == cancellationMode ) { U32 targetRPM = 0; F32 TDi = getTemperatureValue( TEMPSENSORS_INLET_DIALYSATE ); F32 TRo = getTemperatureValue( TEMPSENSORS_OUTLET_REDUNDANT ); // Stop all the actuators first then decide who should run next deenergizeActuators( NO_PARK_CONC_PUMPS ); // The two sensors must be less than a threshold to decide if mix drain is needed to normal drain if ( ( TDi < MIX_DRAIN_TEMPERATURE_THRESHOLD_C ) && ( TRo < MIX_DRAIN_TEMPERATURE_THRESHOLD_C ) ) { targetRPM = DRAIN_PUMP_TARGET_RPM; cancellationMode = CANCELLATION_MODE_COLD; } else { // The fluid is hot so this is a mix drain. Set the VPd to direct the cold inlet fluid to drain setValveState( VPI, VALVE_STATE_OPEN ); setValveState( VPD, VALVE_STATE_DRAIN_C_TO_NO ); targetRPM = DRAIN_PUMP_RPM_IN_MIX_DRAIN; cancellationMode = CANCELLATION_MODE_HOT; } rsrvr1Status = DG_RESERVOIR_ABOVE_TARGET; rsrvr2Status = DG_RESERVOIR_ABOVE_TARGET; haveDrainParamsBeenInit[ DG_RESERVOIR_1 ] = FALSE; haveDrainParamsBeenInit[ DG_RESERVOIR_2 ] = FALSE; stateTimer = getMSTimerCount(); // The drain is set to start from reservoir 2 since all the actuators have been de-energized // Set the drain valve to reservoir 2 setValveState( VRD2, VALVE_STATE_OPEN ); setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); setDrainPumpTargetRPM( targetRPM ); } // If reservoir 2 is empty, set to drain reservoir 1 if ( DG_RESERVOIR_ABOVE_TARGET == rsrvr2Status ) { // If the cancellation water path cannot be done, got to basic cancellation path rsrvr2Status = getRsrvrDrainStatus( DG_RESERVOIR_2, DRAIN_WEIGHT_UNCHANGE_TIMEOUT_MS, drainTimeoutMS ); if ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) { // Set the drain valve to reservoir 1 and close reservoir 2 setValveState( VRD1, VALVE_STATE_OPEN ); setValveState( VRD2, VALVE_STATE_CLOSED ); } } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr2Status ) { // Stop the actuators that are running before going to basic cancellation path setValveState( VRD1, VALVE_STATE_CLOSED ); setValveState( VRD2, VALVE_STATE_CLOSED ); signalDrainPumpHardStop(); prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_BASIC_PATH; } // If reservoir 2 has already been drained and reservoir 1 is empty, reset and switch to complete if ( ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) && ( DG_RESERVOIR_ABOVE_TARGET == rsrvr1Status ) ) { // If the cancellation water path cannot be done, got to basic cancellation path rsrvr1Status = getRsrvrDrainStatus( DG_RESERVOIR_1, DRAIN_WEIGHT_UNCHANGE_TIMEOUT_MS, drainTimeoutMS ); if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) { setValveState( VRD1, VALVE_STATE_CLOSED ); setValveState( VRD2, VALVE_STATE_CLOSED ); signalDrainPumpHardStop(); failHeatDisinfect(); } } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { setValveState( VRD1, VALVE_STATE_CLOSED ); setValveState( VRD2, VALVE_STATE_CLOSED ); signalDrainPumpHardStop(); prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_BASIC_PATH; } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectCompleteState function handles the * heat disinfect complete state. The state stops heat disinfect and * requests transition to mode standby. * @details Inputs: none * @details Outputs: none * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCompleteState( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_COMPLETE; heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_COMPLETE; #ifndef _RELEASE_ if ( nelsonSupport != NELSON_NONE ) { requestNewOperationMode( DG_MODE_STAN ); } else #endif { requestNewOperationMode( DG_MODE_HCOL ); } return state; } /*********************************************************************//** * @brief * The failHeatDisinfect function sets the alarm that failed the heat * disinfect mode. * @details Inputs: alarmDetectedPendingTrigger, prevHeatDisinfectState * @details Outputs: none * @return none *************************************************************************/ static void failHeatDisinfect( void ) { // If a fault alarm is active go to mode fault otherwise for cleaning mode alarms, transition to standby DG_OP_MODE_T nextOpMode = ( FALSE == isDGFaultAlarmActive() ? DG_MODE_STAN : DG_MODE_FAUL ); // In the cleaning modes the alarms are triggered but the mode is not transitioned to fault automatically // so transition to fault mode is done here if ( alarmDetectedPendingTrigger != ALARM_ID_NO_ALARM ) { SET_ALARM_WITH_1_U32_DATA( alarmDetectedPendingTrigger, prevHeatDisinfectState ) } requestNewOperationMode( nextOpMode ); } /*********************************************************************//** * @brief * The getRsrvrFillStatus function checks whether the target reservoir * is full or not. If the fill times out, it sets the state machine to * complete and exits the heat disinfect mode. * @details Inputs: rsrvrFillStableTimeCounter, alarm, stateTimer * @details Outputs: none * @param reservoir is DG_RESERVOIR_1 or DG_RESERVOIR_2 * @param targetVol is the target fill volume * @param timeout is the fill up timeout * @return the status of the reservoirs during filling *************************************************************************/ static DG_RESERVOIR_STATUS_T getRsrvrFillStatus( DG_RESERVOIR_ID_T reservoir, F32 targetVol, U32 timeout ) { DG_RESERVOIR_STATUS_T status = DG_RESERVOIR_BELOW_TARGET; F32 volume = 0.0F; if ( DG_RESERVOIR_1 == reservoir ) { volume = getLoadCellSmallFilteredWeight( LOAD_CELL_RESERVOIR_1_PRIMARY ); } else if ( DG_RESERVOIR_2 == reservoir ) { volume = getLoadCellSmallFilteredWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_DG_RESERVOIR_SELECTED, reservoir ) } // Check the volume of the reservoir against the target volume if ( volume >= targetVol ) { if ( ++rsrvrFillStableTimeCounter >= RSRVRS_FULL_STABLE_TIME_COUNT ) { status = DG_RESERVOIR_REACHED_TARGET; rsrvrFillStableTimeCounter = 0; // Set the state timer in case it needs to be used for another timeout check if ( ( DG_HEAT_DISINFECT_STATE_FLUSH_R2_AND_DRAIN_R1 == heatDisinfectState ) && ( DG_RESERVOIR_2 == reservoir ) ) { if ( rsrvr1Status == DG_RESERVOIR_REACHED_TARGET ) { stateTimer = getMSTimerCount(); } } else { stateTimer = getMSTimerCount(); } } } else if ( TRUE == didTimeout( stateTimer, timeout ) ) { // Failed to fill ontime. Update the previous heat disinfect state and transition to basic cancellation prevHeatDisinfectState = heatDisinfectState; alarmDetectedPendingTrigger = ALARM_ID_DG_RESERVOIR_FILL_TIMEOUT; status = DG_RESERVOIR_NOT_REACHED_TARGET; } return status; } /*********************************************************************//** * @brief * The getRsrvrDrainStatus function returns the status of draining a * reservoir. * @details Inputs: stateTimer, prevHeatDisinfectState, heatDisinfectState, * alarm * @details Outputs: stateTimer, heatDisinfectState, alarm * @param r is DG_RESERVOIR_1 or DG_RESERVOIR_2 * @param drainSteadyStateTimeout which is the time the reservoir's level * does not change and is steady state * @param timeout which is the timeout that a reservoir must be drained by * then * @return the status of the reservoirs during draining *************************************************************************/ static DG_RESERVOIR_STATUS_T getRsrvrDrainStatus( DG_RESERVOIR_ID_T r, U32 drainSteadyStateTimeout, U32 timeout ) { DG_RESERVOIR_STATUS_T status = DG_RESERVOIR_ABOVE_TARGET; BOOL isDrainComplete = FALSE; // If the drain parameters of the reservoir is not initialized, initialize them if ( FALSE == haveDrainParamsBeenInit[ r ] ) { initDrainParameters( r ); haveDrainParamsBeenInit[ r ] = TRUE; } // NOTE: the drain status should be checked once the reservoirs parameters are initialized. This is to make sure the // the timers for stable drain time are initialized prior to using them again isDrainComplete = hasTargetDrainToZeroBeenReached( r, drainSteadyStateTimeout ); if ( TRUE == isDrainComplete ) { if ( ( DG_HEAT_DISINFECT_STATE_FLUSH_R2_AND_DRAIN_R1 == heatDisinfectState ) && ( DG_RESERVOIR_1 == r ) ) { if ( ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) && ( 0 == getDrainPumpTargetRPM() ) ) { stateTimer = getMSTimerCount(); } } else { stateTimer = getMSTimerCount(); } haveDrainParamsBeenInit[ r ] = FALSE; status = DG_RESERVOIR_REACHED_TARGET; } else if ( TRUE == didTimeout( stateTimer, timeout ) ) { // Failed to drain on time. Update the previous heat disinfect state and transition to basic cancellation prevHeatDisinfectState = heatDisinfectState; alarmDetectedPendingTrigger = ALARM_ID_DG_RESERVOIR_DRAIN_TIMEOUT; haveDrainParamsBeenInit[ r ] = FALSE; status = DG_RESERVOIR_NOT_REACHED_TARGET; } return status; } /*********************************************************************//** * @brief * The getHeatDisinfectStatus function monitors and returns the current * stage of heat disinfect cycle. It monitors * 1) The temperature gradient betweeen TPo and Thd * 2) Reservoir 1 or Reservoir 2 have lost more than allowed loss volume * 3) THd must be above the minimum target disinfect temp for the * target disinfect time * 4) The overall heat disinfect time * * @details Inputs: tempGradOutOfRangeTimer, areRsrvrsLeaking, * isPartialDisinfectInProgress, heatDisinfectTimer, * @details Outputs: tempGradOutOfRangeTimer, alarmDetectedPendingTrigger, * areRsrvrsLeaking, isPartialDisinfectInProgress, * heatDisinfectTimer * @return status of the heat disinfect (i.e in progress, complete) *************************************************************************/ static HEAT_DISINFECT_STATUS_T getHeatDisinfectStatus( void ) { HEAT_DISINFECT_STATUS_T status = HEAT_DISINFECT_HEAT_UP_IN_PROGRESS; F32 TPoTempC = getTemperatureValue( TEMPSENSORS_OUTLET_PRIMARY_HEATER ); F32 ThdTempC = getTemperatureValue( TEMPSENSORS_HEAT_DISINFECT ); F32 TDiTempC = getTemperatureValue( TEMPSENSORS_INLET_DIALYSATE ); F32 loadCellA1 = getLoadCellSmallFilteredWeight( LOAD_CELL_RESERVOIR_1_PRIMARY ); F32 loadCellB1 = getLoadCellSmallFilteredWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); BOOL isR1OutOfRange = ( fabs( loadCellA1 - rsrvr1RefVolML ) > RSRVRS_MAX_TARGET_VOL_CHANGE_ML ? TRUE : FALSE ); BOOL isR2OutOfRange = ( fabs( loadCellB1 - rsrvr2RefVolML ) > RSRVRS_MAX_TARGET_VOL_CHANGE_ML ? TRUE : FALSE ); BOOL isGradientOutOfRange = ( fabs( TPoTempC - ThdTempC ) > HEAT_DISINFECT_MAX_TEMP_GRADIENT_C ? TRUE : FALSE ); if ( TRUE == didTimeout( stateTimer, HEAT_DISINFECT_START_TEMP_TIMEOUT_MS ) ) { // Heating up to minimum temperature for heat disinfect failed alarmDetectedPendingTrigger = ALARM_ID_DG_HEAT_DISINFECT_TARGET_TEMP_TIMEOUT; status = HEAT_DISINFECT_HEAT_UP_TIMEOUT; } // Perform check if no pending alarm if ( ALARM_ID_NO_ALARM == alarmDetectedPendingTrigger ) { if ( FALSE == isGradientOutOfRange ) { tempGradOutOfRangeTimer = 0; } else if ( 0 == tempGradOutOfRangeTimer ) { if ( ( TRUE == didTimeout( stateTimer, HEAT_DIS_TEMP_GRAD_WAIT_2_CHECK_TIME_MS ) ) && ( DG_HEAT_DISINFECT_STATE_DISINFECT_R1_TO_R2 == heatDisinfectState ) ) { tempGradOutOfRangeTimer = getMSTimerCount(); } else if ( DG_HEAT_DISINFECT_STATE_DISINFECT_R2_TO_R1 == heatDisinfectState ) { tempGradOutOfRangeTimer = getMSTimerCount(); } } else if ( TRUE == didTimeout( tempGradOutOfRangeTimer, HEAT_DISINFECT_TEMP_GRAD_OUT_RANGE_TIME_MS ) ) { alarmDetectedPendingTrigger = ALARM_ID_DG_HEAT_DISINFECT_TEMP_GRAD_OUT_OF_RANGE; status = HEAT_DISINFECT_TEMP_GRADIENT_OUT_OF_RANGE; } } // Perform check if no pending alarm if ( ALARM_ID_NO_ALARM == alarmDetectedPendingTrigger ) { if ( ( rsrvr1RefVolML > NEARLY_ZERO ) && ( rsrvr2RefVolML > NEARLY_ZERO ) ) { // Check if either reservoir 1 or reservoir 2 are losing volume more than allowed volume if ( ( TRUE == isR1OutOfRange ) || ( TRUE == isR2OutOfRange ) ) { // If the leak is the first time after a while, set the flag and start the timer if ( FALSE == areRsrvrsLeaking ) { areRsrvrsLeaking = TRUE; rsrvrsVolMonitorTimer = getMSTimerCount(); } // If the volume is out of range and it has timed out, exit else if ( TRUE == didTimeout( rsrvrsVolMonitorTimer, RSRVRS_TARGET_VOL_OUT_TIMEOUT_MS ) ) { areRsrvrsLeaking = FALSE; alarmDetectedPendingTrigger = ALARM_ID_DG_RESERVOIR_LEAK_TIMEOUT; status = HEAT_DISINFECT_RSRVRS_LEAK_TIMEOUT; } } } // Reservoirs are in range else { areRsrvrsLeaking = FALSE; } } if ( ALARM_ID_NO_ALARM == alarmDetectedPendingTrigger ) { #ifndef _RELEASE_ if ( ( nelsonSupport != NELSON_INOCULATE ) && ( nelsonSupport != NELSON_POS_CONTROL_HEAT_DISINFECT ) ) #endif { if ( TDiTempC >= HEAT_DISINFECT_TRIMMER_HEATER_STOP_TEMP_C ) { stopHeater( DG_TRIMMER_HEATER ); } else if ( FALSE == isHeaterOn( DG_TRIMMER_HEATER ) ) { setHeaterTargetTemperature( DG_TRIMMER_HEATER, HEAT_DISINFECT_TRIM_HEATER_TARGET_TEMP_C ); startHeater( DG_TRIMMER_HEATER ); } } } // Perform check if no pending alarm if ( ALARM_ID_NO_ALARM == alarmDetectedPendingTrigger ) { BOOL disinfectStatus = FALSE; if ( TPoTempC >= concPumpsStartTemperatureC ) { F32 acidSpeed = getMeasuredPumpSpeedMLPM( CONCENTRATEPUMPS_CP1_ACID ); F32 bicarbSpeed = getMeasuredPumpSpeedMLPM( CONCENTRATEPUMPS_CP2_BICARB ); if ( ( acidSpeed < NEARLY_ZERO ) || ( bicarbSpeed < NEARLY_ZERO ) ) { // Turn the pumps on in reverse setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP1_ACID, ACID_PUMP_SPEED_ML_PER_MIN ); setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP2_BICARB, BICARB_PUMP_SPEED_ML_PER_MIN ); // During R1 to R2 disinfect, the concentrate pumps turn on requestConcentratePumpOn( CONCENTRATEPUMPS_CP1_ACID ); requestConcentratePumpOn( CONCENTRATEPUMPS_CP2_BICARB ); } } // Check whether the reservoir under disinfect has elapsed time. disinfectStatus |= hasDisinfectTimeElapsed( RSRVR_AT_77_C ); disinfectStatus |= hasDisinfectTimeElapsed( RSRVR_AT_82_C ); // If either of the reservoir's disinfect time has elapsed, call this section complete status = ( TRUE == disinfectStatus ? HEAT_DISINFECT_COMPLETE : status ); } return status; } /*********************************************************************//** * @brief * The hasDisinfectTimeElapsed function checks whether the disinfect time of * either reservoirs or the RO filter has been elapsed. * @details Inputs: timeStatus * @details Outputs: timeStatus * @param disinfectTime which is the time to check (i.e reservoir at 77 C or * RO disinfect at 82 C) * @return TRUE if the specified time has been elapsed otherwise FALSE *************************************************************************/ static BOOL hasDisinfectTimeElapsed( HEAT_DISINFECT_TIMES_T disinfectTime ) { BOOL status = FALSE; F32 temperatureValue = getTemperatureValue( timeStatus[ disinfectTime ].tempSensor ); if ( temperatureValue >= timeStatus[ disinfectTime ].startTempC ) { // If the temperature of the specified temperature sensor has elapsed the start temperature and the start timer is in reset (it is 0) // start the timer. // If the timer has started and time of the disinfect has elapsed, set the status to TRUE. if ( 0 == timeStatus[ disinfectTime ].startTimeMS ) { timeStatus[ disinfectTime ].startTimeMS = getMSTimerCount(); } else if ( TRUE == didTimeout( timeStatus[ disinfectTime ].startTimeMS, timeStatus[ disinfectTime ].targetTimeMS ) ) { status = TRUE; } } else if ( temperatureValue < timeStatus[ disinfectTime ].stopTempC ) { // If the temperature dropped below the target, reset the timer timeStatus[ disinfectTime ].startTimeMS = 0; } return status; } /*********************************************************************//** * @brief * The publishHeatDisinfectData function publishes heat disinfect data at * the set interval. * @details Inputs: dataPublishCounter * @details Outputs: dataPublishCounter * @return: none *************************************************************************/ static void publishHeatDisinfectData( void ) { if ( ++dataPublishCounter >= HEAT_DISINFECT_DATA_PUB_INTERVAL ) { MODE_HEAT_DISINFECT_DATA_T data; MODE_HEAT_DISINFECT_UI_DATA_T uiData; data.heatDisinfectState = (U32)heatDisinfectState; data.overallElapsedTime = calcTimeSince( overallHeatDisinfectTimer ); data.stateElapsedTime = calcTimeSince( stateTimer ); data.cancellationMode = (U32)cancellationMode; data.heatDisinfectUIState = (U32)heatDisinfectUIState; uiData.heatDisinfectTargetTime = targetDisinfectTime; uiData.ro77CountdownTimeS = ( 0 == timeStatus[ RO_AT_77_C ].startTimeMS ? 0 : ( timeStatus[ RO_AT_77_C ].targetTimeMS - calcTimeSince( timeStatus[ RO_AT_77_C ].startTimeMS ) ) / 1000 ); // The count down is converted into seconds since the UI does not work with milliseconds uiData.ro82CountdownTimeS = ( 0 == timeStatus[ RO_AT_82_C ].startTimeMS ? 0 : ( timeStatus[ RO_AT_82_C ].targetTimeMS - calcTimeSince( timeStatus[ RO_AT_82_C ].startTimeMS ) ) / 1000 ); uiData.r77CountdownTimeS = ( 0 == timeStatus[ RSRVR_AT_77_C ].startTimeMS ? 0 : ( timeStatus[ RSRVR_AT_77_C ].targetTimeMS - calcTimeSince( timeStatus[ RSRVR_AT_77_C ].startTimeMS ) ) / 1000 ); uiData.r82CountdownTimeS = ( 0 == timeStatus[ RSRVR_AT_82_C ].startTimeMS ? 0 : ( timeStatus[ RSRVR_AT_82_C ].targetTimeMS - calcTimeSince( timeStatus[ RSRVR_AT_82_C ].startTimeMS ) ) / 1000 ); data.R1FillLevel = rsrvr1RefVolML; data.R2FillLevel = rsrvr2RefVolML; broadcastData( MSG_ID_DG_HEAT_DISINFECT_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&data, sizeof( MODE_HEAT_DISINFECT_DATA_T ) ); broadcastData( MSG_ID_DG_HEAT_DISINFECT_TIME_DATA, COMM_BUFFER_OUT_CAN_DG_2_UI, (U08*)&uiData, sizeof( MODE_HEAT_DISINFECT_UI_DATA_T ) ); dataPublishCounter = 0; } } /*********************************************************************//** * @brief * The monitorModeHeatDisinfect function monitors the status of the caps and * sets the state of the state machine to water cancellation path if the caps * are not closed during the run. * @details Inputs: none * @details Outputs: prevHeatDisinfectState, heatDisinfectState, * alarmDetectedPendingTrigger * @return: none *************************************************************************/ static void monitorModeHeatDisinfect( void ) { // Constantly check the status of the RO disinfect isRODisinfectDone |= hasDisinfectTimeElapsed( RO_AT_77_C ); isRODisinfectDone |= hasDisinfectTimeElapsed( RO_AT_82_C ); #ifndef _RELEASE_ if ( ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_CAPS_MONITOR ) != SW_CONFIG_ENABLE_VALUE ) && ( nelsonSupport != NELSON_INOCULATE ) ) #endif { if ( ( STATE_OPEN == getSwitchStatus( CONCENTRATE_CAP ) ) || ( STATE_OPEN == getSwitchStatus( DIALYSATE_CAP ) ) ) { // Set the variables to fail and go to cancel water path. Set the pending alarm to no alarm so the cancel water path // will not be raising the alarm at end of the cancel water path. The recoverable alarm is raised here in this function prevHeatDisinfectState = heatDisinfectState; heatDisinfectState = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; alarmDetectedPendingTrigger = ALARM_ID_DG_DIALYSATE_CAP_NOT_IN_PROPER_POSITION; if ( STATE_OPEN == getSwitchStatus( CONCENTRATE_CAP ) ) { alarmDetectedPendingTrigger = ALARM_ID_DG_CONCENTRATE_CAP_NOT_IN_PROPER_POSITION; } } } if ( ( TRUE == isDGFaultAlarmActive() ) || ( TRUE == isAnyCleaningModeInletWaterConditionActive() ) ) { if ( heatDisinfectState != DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH ) { prevHeatDisinfectState = heatDisinfectState; heatDisinfectState = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } } } /*********************************************************************//** * @brief * The writeDisinfectDataToNV function writes the disinfection data to the * non-volatile memory. * @details Inputs: disinfectNVOps * @details Outputs: disinfectNVOps * @param info the type disinfect data to write to the memory (i.e. heat * disinfect start time) * @return: none *************************************************************************/ static void writeDisinfectDataToNV( DG_USAGE_INFO_ITEMS_T info ) { if ( FALSE == disinfectNVOps.hasDisStatusBeenWrittenToNV ) { disinfectNVOps.hasDisStatusBeenWrittenToNV = setLastDisinfectDate( info, getRTCTimestamp() ); } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testSetDG77CStateTimerOverride function checks whether the disinfection * time of either reservoirs or the RO filter has been elapsed. * @details Inputs: none * @details Outputs: targetTimer77C * @param timer which is the time to check at 77 C * @return TRUE if request successful, FALSE if not *************************************************************************/ BOOL testSetDG77CStateTimerOverride( U32 timer ) { BOOL result = FALSE; if ( ( TRUE == isTestingActivated() ) && ( timer >= HEAT_DISNFECT_MIN_OVERRIDE_TIME_MS ) ) { targetTimer77C.ovData = timer; targetTimer77C.override = OVERRIDE_KEY; result = TRUE; } return result; } /*********************************************************************//** * @brief * The testResetDG77CStateTimerOverride function checks whether the disinfection * time of either reservoirs or the RO filter has been elapsed. * @details Inputs: none * @details Outputs: targetTimer77C * @param timer which is the time to check at 77 C * @return TRUE if request successful, FALSE if not *************************************************************************/ BOOL testResetDG77CStateTimerOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { targetTimer77C.override = OVERRIDE_RESET; targetTimer77C.ovData = targetTimer77C.ovInitData; result = TRUE; } return result; } /*********************************************************************//** * @brief * The testSetDG82CStateTimerOverride function checks whether the disinfection * time of either reservoirs or the RO filter has been elapsed. * @details Inputs: none * @details Outputs: targetTimer82C * @param timer which is the time to check at 82 C * @return TRUE if request successful, FALSE if not *************************************************************************/ BOOL testSetDG82CStateTimerOverride( U32 timer ) { BOOL result = FALSE; if ( ( TRUE == isTestingActivated() ) && ( timer >= HEAT_DISNFECT_MIN_OVERRIDE_TIME_MS ) ) { targetTimer82C.ovData = timer; targetTimer82C.override = OVERRIDE_KEY; result = TRUE; } return result; } /*********************************************************************//** * @brief * The testResetDG82CStateTimerOverride function checks whether the disinfection * time of either reservoirs or the RO filter has been elapsed. * @details Inputs: none * @details Outputs: targetTimer82C * @param timer which is the time to check at 82 C * @return TRUE if request successful, FALSE if not *************************************************************************/ BOOL testResetDG82CStateTimerOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { targetTimer82C.override = OVERRIDE_RESET; targetTimer82C.ovData = targetTimer82C.ovInitData; result = TRUE; } return result; } /**@}*/ // ********** Nelson Support Functions ********** #ifndef _RELEASE_ /*********************************************************************//** * @brief * The setNelsonSupportConditions function sets the disinfect variables for * Nelson support. * @details Inputs: nelsonSupport * @details Outputs: timeStatus, heatDisinfectState, concPumpsStartTemperatureC * @return: none *************************************************************************/ static void setNelsonSupportConditions( void ) { switch ( nelsonSupport ) { case NELSON_INOCULATE: concPumpsStartTemperatureC = NELSON_SUPPORT_TARGET_TEMP_C; timeStatus[ RO_AT_77_C ].startTempC = NELSON_SUPPORT_TARGET_TEMP_C; timeStatus[ RO_AT_77_C ].startTimeMS = 0; timeStatus[ RO_AT_77_C ].stopTempC = NELSON_SUPPORT_STOP_TEMP_C; timeStatus[ RO_AT_77_C ].targetTimeMS = NELSON_SUPPORT_INOCULATE_TIME_MS; timeStatus[ RO_AT_77_C ].tempSensor = TEMPSENSORS_HEAT_DISINFECT; timeStatus[ RO_AT_82_C ].startTempC = NELSON_SUPPORT_TARGET_TEMP_C; timeStatus[ RO_AT_82_C ].startTimeMS = 0; timeStatus[ RO_AT_82_C ].stopTempC = NELSON_SUPPORT_STOP_TEMP_C; timeStatus[ RO_AT_82_C ].targetTimeMS = NELSON_SUPPORT_INOCULATE_TIME_MS; timeStatus[ RO_AT_82_C ].tempSensor = TEMPSENSORS_HEAT_DISINFECT; timeStatus[ RSRVR_AT_77_C ].startTempC = NELSON_SUPPORT_TARGET_TEMP_C; timeStatus[ RSRVR_AT_77_C ].startTimeMS = 0; timeStatus[ RSRVR_AT_77_C ].stopTempC = NELSON_SUPPORT_STOP_TEMP_C; // Set 82 C time to be the shorter time since the entire inoculation time should be 30 mins but the 77 C is 32 minutes timeStatus[ RSRVR_AT_77_C ].targetTimeMS = HEAT_DISINFECT_AT_82_C_TIME_MS; timeStatus[ RSRVR_AT_77_C ].tempSensor = TEMPSENSORS_INLET_DIALYSATE; timeStatus[ RSRVR_AT_82_C ].startTempC = NELSON_SUPPORT_TARGET_TEMP_C; timeStatus[ RSRVR_AT_82_C ].startTimeMS = 0; timeStatus[ RSRVR_AT_82_C ].stopTempC = NELSON_SUPPORT_STOP_TEMP_C; timeStatus[ RSRVR_AT_82_C ].targetTimeMS = HEAT_DISINFECT_AT_82_C_TIME_MS; timeStatus[ RSRVR_AT_82_C ].tempSensor = TEMPSENSORS_INLET_DIALYSATE; break; case NELSON_POS_CONTROL_HEAT_DISINFECT: concPumpsStartTemperatureC = NELSON_SUPPORT_TARGET_TEMP_C; timeStatus[ RO_AT_77_C ].startTempC = NELSON_SUPPORT_TARGET_TEMP_C; timeStatus[ RO_AT_77_C ].startTimeMS = 0; timeStatus[ RO_AT_77_C ].stopTempC = NELSON_SUPPORT_STOP_TEMP_C; timeStatus[ RO_AT_77_C ].targetTimeMS = NELSON_SUPPORT_DISINFECT_TIME_MS; timeStatus[ RO_AT_77_C ].tempSensor = TEMPSENSORS_HEAT_DISINFECT; timeStatus[ RO_AT_82_C ].startTempC = NELSON_SUPPORT_TARGET_TEMP_C; timeStatus[ RO_AT_82_C ].startTimeMS = 0; timeStatus[ RO_AT_82_C ].stopTempC = NELSON_SUPPORT_STOP_TEMP_C; timeStatus[ RO_AT_82_C ].targetTimeMS = NELSON_SUPPORT_DISINFECT_TIME_MS; timeStatus[ RO_AT_82_C ].tempSensor = TEMPSENSORS_HEAT_DISINFECT; timeStatus[ RSRVR_AT_77_C ].startTempC = NELSON_SUPPORT_TARGET_TEMP_C; timeStatus[ RSRVR_AT_77_C ].startTimeMS = 0; timeStatus[ RSRVR_AT_77_C ].stopTempC = NELSON_SUPPORT_STOP_TEMP_C; timeStatus[ RSRVR_AT_77_C ].targetTimeMS = HEAT_DISINFECT_AT_82_C_TIME_MS; timeStatus[ RSRVR_AT_77_C ].tempSensor = TEMPSENSORS_INLET_DIALYSATE; timeStatus[ RSRVR_AT_82_C ].startTempC = NELSON_SUPPORT_TARGET_TEMP_C; timeStatus[ RSRVR_AT_82_C ].startTimeMS = 0; timeStatus[ RSRVR_AT_82_C ].stopTempC = NELSON_SUPPORT_STOP_TEMP_C; timeStatus[ RSRVR_AT_82_C ].targetTimeMS = HEAT_DISINFECT_AT_82_C_TIME_MS; timeStatus[ RSRVR_AT_82_C ].tempSensor = TEMPSENSORS_INLET_DIALYSATE; setValveState( VPI, VALVE_STATE_CLOSED ); setValveState( VBF, VALVE_STATE_OPEN ); setValveState( VPD, VALVE_STATE_DRAIN_C_TO_NO ); setValveState( VDR, VALVE_STATE_RECIRC_C_TO_NC ); setValveState( VRC, VALVE_STATE_RECIRC_C_TO_NC ); setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); setValveState( VRD1, VALVE_STATE_CLOSED ); setValveState( VRD2, VALVE_STATE_OPEN ); signalROPumpHardStop(); setDrainPumpTargetOutletFlowLPM( HEAT_DISINFECT_TARGET_RO_FLOW_LPM ); heatDisinfectState = DG_HEAT_DISINFECT_STATE_DISINFECT_R1_TO_R2; break; case NELSON_HEAT_DISINFECT: // Set the valves to drain R2 and no fill setValveState( VPI, VALVE_STATE_CLOSED ); setValveState( VBF, VALVE_STATE_OPEN ); setValveState( VPD, VALVE_STATE_DRAIN_C_TO_NO ); setValveState( VDR, VALVE_STATE_RECIRC_C_TO_NC ); setValveState( VRC, VALVE_STATE_RECIRC_C_TO_NC ); setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); setValveState( VRD1, VALVE_STATE_CLOSED ); setValveState( VRD2, VALVE_STATE_OPEN ); signalROPumpHardStop(); setDrainPumpTargetOutletFlowLPM( HEAT_DISINFECT_TARGET_RO_FLOW_LPM ); setHeaterTargetTemperature( DG_PRIMARY_HEATER, HEAT_DISINFECT_PRIM_HEATER_TARGET_TEMP_C ); setHeaterTargetTemperature( DG_TRIMMER_HEATER, HEAT_DISINFECT_TRIM_HEATER_TARGET_TEMP_C ); startHeater( DG_PRIMARY_HEATER ); startHeater( DG_TRIMMER_HEATER ); heatDisinfectState = DG_HEAT_DISINFECT_STATE_DISINFECT_R1_TO_R2; break; case NELSON_DRAIN_SAMPLES: heatDisinfectState = DG_HEAT_DISINFECT_STATE_START; break; case NELSON_NONE: default: // Do nothing for these cases. break; } } /*********************************************************************//** * @brief * The handleNelsonHeatDisinfectFillR1WithWaterState function handles the Nelson * fill reservoir 1 with water. * @details Inputs: rsrvr1Status, rsrvr2Status * @details Outputs: rsrvr1Status, rsrvr2Status, prevHeatDisinfectState * @return next state of the heat disinfect state machine *************************************************************************/ static DG_HEAT_DISINFECT_STATE_T handleNelsonHeatDisinfectFillR1WithWaterState( void ) { DG_HEAT_DISINFECT_STATE_T state = DG_NELSON_HEAT_DISINFECT_STATE_FILL_R1_WITH_WATER; // First reservoir 2 must be partially full if ( DG_RESERVOIR_BELOW_TARGET == rsrvr2Status ) { rsrvr2Status = getRsrvrFillStatus( DG_RESERVOIR_2, RSRVRS_PARTIAL_FILL_VOL_ML, RSRVRS_500ML_FILL_UP_TIMEOUT_MS ); } else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) { rsrvr1Status = getRsrvrFillStatus( DG_RESERVOIR_1, RSRVRS_FULL_VOL_ML, RSRVRS_FILL_UP_TIMEOUT_MS ); if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) { deenergizeActuators( NO_PARK_CONC_PUMPS ); state = DG_HEAT_DISINFECT_STATE_COMPLETE; } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } } else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr2Status ) { prevHeatDisinfectState = state; state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; } return state; } #endif #ifndef _RELEASE_ /*********************************************************************//** * @brief * The setHeatNelsonSupportMode function sets the requested Nelson support * mode (i.e. inoculate, ...) * @details Inputs: none * @details Outputs: nelsonSupport * @param support the type Nelson support (i.e. inoculate, heat disinfect) * @return none *************************************************************************/ void setHeatNelsonSupportMode( NELSON_SUPPORT_T support ) { nelsonSupport = support; } #endif