Index: firmware/App/Modes/ModeHeatDisinfect.c =================================================================== diff -u -r5fc16235c1752c993b3f1285f3a2b9738372af7a -rc3dad292358c336c5b37716292f84d79b9881e2f --- firmware/App/Modes/ModeHeatDisinfect.c (.../ModeHeatDisinfect.c) (revision 5fc16235c1752c993b3f1285f3a2b9738372af7a) +++ firmware/App/Modes/ModeHeatDisinfect.c (.../ModeHeatDisinfect.c) (revision c3dad292358c336c5b37716292f84d79b9881e2f) @@ -1,28 +1,40 @@ /************************************************************************** * -* Copyright (c) 2019-2020 Diality Inc. - All Rights Reserved. +* Copyright (c) 2020-2022 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 +* @file ModeHeatDisinfect.c * -* @author (last) Quang Nguyen -* @date (last) 01-Sep-2020 +* @author (last) Dara Navaei +* @date (last) 21-Nov-2022 * -* @author (original) Sean -* @date (original) 20-Apr-2020 +* @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" @@ -36,41 +48,135 @@ // ********** private definitions ********** // General defines -#define MAX_ALLOWED_STATE_TRIALS 1 ///< Max allowed trials on a state. This is general among all the states. +#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. // Start state defines -#define MIN_INLET_PRESSURE_PSI 30.0 ///< Minimum water inlet pressure in psi. -#define MAX_START_STATE_TEMP_SENSORS_DIFF_C 1.0 ///< Max start state TDi and TRo difference tolerance in C. +#define MIN_INLET_PRESSURE_PSI 30.0F ///< Minimum water inlet pressure in psi. +#define MAX_START_STATE_TEMP_SENSORS_DIFF_C 3.0F ///< Max start state TDi and TRo difference tolerance in C. // Drain R1 & R2 states defines -#define DRAIN_PUMP_TARGET_RPM 2500 ///< Drain pump target RPM during drain. -#define DRAIN_RESERVOIRS_TIME_OUT_MS ( 60 * MS_PER_SECOND ) ///< Drain reservoirs time out in milliseconds. -#define EMPTY_RESERVOIRS_WEIGHT_GRAMS 50 ///< The weight of an empty reservoir. //TODO Change this value to actual value +#define DRAIN_PUMP_TARGET_RPM 2400 ///< Drain pump target RPM during drain. +#define RSRVRS_INITIAL_DRAIN_TIME_OUT_MS ( 2 * SEC_PER_MIN * MS_PER_SECOND ) ///< Reservoirs 1 & 2 initial drain time out in milliseconds. +#define DRAIN_WEIGHT_UNCHANGE_TIMEOUT ( 6 * MS_PER_SECOND ) ///< Time period of unchanged weight during draining before timeout. // Flush drain path state defines -#define FLUSH_DRAIN_WAIT_TIME_MS ( 60 * MS_PER_SECOND ) ///< Flush Drain path wait time in milliseconds. -#define MIN_INLET_TEMPERATURE_C 25.0 ///< Minimum water inlet temperature in C. -#define MIN_INLET_CONDUCTIVITY_S_PER_CM 0.0 ///< Minimum water inlet conductivity in mS/cm? TODO find out the real value +#define FLUSH_DRAIN_WAIT_TIME_MS ( SEC_PER_MIN * MS_PER_SECOND ) ///< Flush Drain path wait time in milliseconds. +#define MIN_INLET_TEMPERATURE_C 15.0F ///< Minimum water inlet temperature in C. TODO original temperature was 25 C +#define MAX_INLET_CONDUCTIVITY_US_PER_CM 2000.0F ///< Maximum water inlet conductivity in us/cm // Flush circulation path state defines -#define RO_PUMP_TARGET_FLOW_RATE_LPM 0.8 ///< RO pump target flow rate in L/min. -#define MAX_RO_PUMP_PRESSURE_PSI 130 ///< Maximum RO pump pressure in psi. -#define FLUSH_CICRCULATION_WAIT_TIME_MS ( 30 * MS_PER_SECOND ) ///< Flush circulation path wait time in milliseconds. -#define MAX_FLUSH_CIRC_TEMP_SENSOR_DIFF_C 3.0 ///< Maximum flush circulation temperature difference tolerance in C. +#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 ( 30 * MS_PER_SECOND ) ///< Flush/rinse circulation path wait time in milliseconds. TODO original time was 30 seconds +#define MAX_FLUSH_CIRC_TEMP_SENSOR_DIFF_C 50.0F ///< Maximum flush circulation temperature difference tolerance in C. TODO original difference was 3.0 degrees +#define NUM_OF_TEMP_SENSORS_TO_AVG 4.0F ///< Number of temperature sensors to average to check the difference. +#define ACID_PUMP_SPEED_ML_PER_MIN -30.0F ///< Acid concentrate pump speed in mL/min. +// The bicarb pump is 2% faster than the acid pump to create a flow from acid to bicarb line during heat disinfect +#define BICARB_PUMP_SPEED_ML_PER_MIN 30.6F ///< 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. TODo original value was 1900 +#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. TODO original value was 5 mins +#define RSRVRS_500ML_FILL_UP_TIMEOUT_MS ( 4 * SEC_PER_MIN * MS_PER_SECOND ) ///< Reservoirs 1 & 2 partial fill up timeout in ms. TODO original value was 2 mins +#define RSRVRS_DRAIN_TIMEOUT_MS ( 2 * SEC_PER_MIN * MS_PER_SECOND ) ///< Reservoirs 1 & 2 drain timeout in ms. +// Fill and heat water +#define HEAT_DISINFECT_TARGET_TEMPERATURE_C 82.0F ///< Heat disinfect target water temperature in C. +#define HEAT_DISINFECT_START_TEMPERATURE_C 81.0F ///< Heat disinfect minimum acceptable temperature 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.8F ///< 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_TARGET_DRAIN_PRES_PSI 12.0F ///< Heat disinfect target drain outlet pressure in psi. +#define HEAT_DISINFECT_TARGET_DRAIN_FILL_R2_PSI 9.0F ///< Heat disinfect target drain R2 fill outplet pressure in PSI +#define HEAT_DISINFECT_TIME_MS ( 10 * SEC_PER_MIN * MS_PER_SECOND ) ///< Heat disinfect time for each section in milliseconds. +#define HEAT_DISINFECT_START_TEMP_TIMOUT_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 ( 0.5F * SEC_PER_MIN * MS_PER_SECOND ) ///< Reservoirs 1 & 2 maximum volume out of range timeout during heat disinfect. TODO change this to 5 seconds +#define RSRVRS_MAX_TARGET_VOL_CHANGE_ML 250.0F // TODO temporary change. Change back to 100 ///< 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. +// Cool down RO filter +#define THD_REACH_BELOW_45_AFTER_CIRC_TIME_MS ( 5 * MS_PER_SECOND ) ///< Number of circulations that are needed to make the RO filter is below 45 C. +#define ROF_COOL_DOWN_TARGET_FLOW_LPM 0.3F ///< RO filter cool down target flow in L/min. +#define ROF_COOL_DOWN_CIRCULATION_TIME_MS ( 2 * SEC_PER_MIN * MS_PER_SECOND ) ///< RO filter cool down circulation timer in milliseconds. +#define ROF_COOL_DOWN_MAX_TIME_MS ( 4 * MIN_PER_HOUR * SEC_PER_MIN * MS_PER_SECOND ) ///< RO filter cool down maximum state time in milliseconds. +#define TARGET_THD_SENSOR_FOR_RINSING_C 44.0F ///< Target THd temperature sensor value before rinsing in C. +// 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. +// Rinse R1 to R2 +#define ROF_MIN_LOW_PRESSURE_TEMPERATURE_C 45.0F ///< RO filter minimum temperature that the pressure must be no more than 30psi in C. + +#define MIX_DRAIN_TEMPERATURE_THRESHOLD_C 60.0F ///< Temperature threshold for performing mix drain or normal drain. + +/// Cancellation paths +typedef enum Cancellation_modes +{ + CANCELLATION_MODE_NONE = 0, ///< Cancellation mode none. + CANCELLATION_MODE_BASIC, ///< Cancellation mode basic. + CANCELLATION_MODE_HOT, ///< Cancellation mode hot. + CANCELLATION_MODE_COLD, ///< Cancellation mode cold. + NUM_OF_CANCELLATION_MODES ///< Number of cancellation modes. +} CANCELLATION_MODE_T; + +/// 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; + +/// Non-volatile write structure +typedef struct +{ + BOOL hasDisStatusBeenWrittenToNV; ///< Boolean flag to indicate whether the disinfect status been written to NV or not. + BOOL hasDisCompleteDateBeenWrittenToNV; ///< Boolean flag to indicate whether the disinfect complete date been written to NV or not. +} DISINFECT_NV_OPS_T; + // ********** private data ********** -static DG_HEAT_DISINFECT_STATE_T heatDisinfectState = DG_HEAT_DISINFECT_STATE_START; ///< Currently active heat disinfect state. -static U32 stateTimer = 0; ///< Heat disinfect state timer to be used in different states. -static U32 stateTrialCounter = 0; ///< Heat disinfect state trial counter to be used for retries in different states. -static BOOL areTempSensorsInRange = FALSE; ///< Heat disinfect temperature sensors in/out range flag. +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. /// Boolean flag to check whether draining R1 and R2 is at the end of the heat disinfect cycle or in the beginning. So the drain states can be reused. -static BOOL isThisLastDrain = FALSE; +static DG_RESERVOIR_STATUS_T rsrvr1Status; ///< Reservoir 1 status. +static DG_RESERVOIR_STATUS_T rsrvr2Status; ///< Reservoir 2 status. +static F32 R1HeatDisinfectVol; ///< Reservoir 1 full volume during heat disinfect. +static F32 R2HeatDisinfectVol; ///< Reservoir 2 full volume during heat disinfect. +static U32 heatDisinfectTimer; ///< Heat disinfect timer. +static BOOL isPartialDisinfectInProgress; ///< Heat disinfect partial complete/in progess flag. +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 ROFCoolingTimer; ///< RO filter cooling timer. +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. // ********** private function prototypes ********** @@ -79,25 +185,76 @@ 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 handleHeatDisinfectFillR2WithHotWaterState( void ); +static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectDisinfectR2ToR1State( void ); +static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCoolDownHeatersState( void ); +static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCoolDownROFilterState( 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 resetActuators( void ); -static void setModeToFailed( DG_HEAT_DISINFECT_STATE_T failedState ); +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 void publishHeatDisinfectData( void ); +static void monitorModeHeatDisinfect( void ); +static void writeDisinfectDataToNV( void ); /*********************************************************************//** * @brief - * The initHeatDisinfectMode function initializes the heat disinfect mode module. - * @details Inputs: none TODO update as we go - * @details Outputs: Initialized heat disinfect mode module + * The initHeatDisinfectMode function initializes the heat disinfect mode + * module. + * @details Inputs: none + * @details Outputs: heatDisinfectState, stateTimer, + * stateTrialCounter, areTempSensorsInRange, rsrvr1Status, rsrvr2Status, + * R1HeatDisinfectVol, R2HeatDisinfectVol, overallHeatDisinfectTimer, + * cancellationMode, rsrvrFillStableTimeCounter, prevHeatDisinfectState + * isPartialDisinfectInProgress, isDrainPumpOnInMixDrain, heatDisinfectTimer + * hasROFCirculationBeenStarted, ROFCirculationTimer, targetDisinfectTime + * ROFCirculationCoolingCounter, concentratePumpsPrimeTimer, areRsrvrsLeaking + * haveDrainParamsBeenInit, tempGradOutOfRangeTimer, disinfectNVOps, + * dataPublishCounter * @return none *************************************************************************/ void initHeatDisinfectMode( void ) { - heatDisinfectState = DG_HEAT_DISINFECT_STATE_START; - - stateTimer = 0; - isThisLastDrain = FALSE; - stateTrialCounter = 0; - areTempSensorsInRange = FALSE; + heatDisinfectState = DG_HEAT_DISINFECT_STATE_START; + prevHeatDisinfectState = DG_HEAT_DISINFECT_STATE_START; + heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_NOT_RUNNING; + stateTimer = 0; + stateTrialCounter = 0; + areTempSensorsInRange = FALSE; + rsrvr1Status = NUM_OF_DG_RESERVOIR_STATUS; + rsrvr2Status = NUM_OF_DG_RESERVOIR_STATUS; + R1HeatDisinfectVol = 0.0F; + R2HeatDisinfectVol = 0.0F; + overallHeatDisinfectTimer = 0; + cancellationMode = CANCELLATION_MODE_NONE; + rsrvrFillStableTimeCounter = 0; + isPartialDisinfectInProgress = FALSE; + isDrainPumpInMixDrainOn = FALSE; + ROFCoolingTimer = 0; + concentratePumpsPrimeTimer = 0; + targetDisinfectTime = 0; + haveDrainParamsBeenInit[ DG_RESERVOIR_1 ] = FALSE; + haveDrainParamsBeenInit[ DG_RESERVOIR_2 ] = FALSE; + tempGradOutOfRangeTimer = 0; + disinfectNVOps.hasDisCompleteDateBeenWrittenToNV = FALSE; + disinfectNVOps.hasDisStatusBeenWrittenToNV = FALSE; + alarmDetectedPendingTrigger = ALARM_ID_NO_ALARM; + heatDisinfectTimer = 0; + rsrvrsVolMonitorTimer = 0; + areRsrvrsLeaking = FALSE; + dataPublishCounter = 0; } /*********************************************************************//** @@ -106,11 +263,17 @@ * heat disinfect mode. * @details Inputs: none * @details Outputs: none - * @return none + * @return initial state *************************************************************************/ -void transitionToHeatDisinfectMode( void ) +U32 transitionToHeatDisinfectMode( void ) { + deenergizeActuators(); + initHeatDisinfectMode(); + + setCPLDCleanLEDColor( CPLD_CLEAN_LED_ORANGE ); + + return heatDisinfectState; } /*********************************************************************//** @@ -123,7 +286,7 @@ *************************************************************************/ U32 execHeatDisinfectMode( void ) { - checkInletPressureFault(); // TODO what is this? + monitorModeHeatDisinfect(); switch ( heatDisinfectState ) { @@ -148,45 +311,63 @@ break; case DG_HEAT_DISINFECT_STATE_FLUSH_R1_AND_R2: + heatDisinfectState = handleHeatDisinfectFlushR1AndR2State(); break; - case DG_HEAT_DISINFECT_STATE_FLUSH_R2_AND_DRAIN_R2: + 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_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_MIX_DRAIN_R1: + case DG_HEAT_DISINFECT_STATE_COOL_DOWN_HEATERS: + heatDisinfectState = handleHeatDisinfectCoolDownHeatersState(); break; - case DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R2: + case DG_HEAT_DISINFECT_STATE_COOL_DOWN_RO_FILTER: + heatDisinfectState = handleHeatDisinfectCoolDownROFilterState(); break; - case DG_HEAT_DISINFECT_STATE_RINSE_R1_TO_R2: + case DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R1: + heatDisinfectState = handleHeatDisinfectMixDrainR1State(); break; - case DG_HEAT_DISINFECT_STATE_RINSE_R2_TO_R1_AND_DRAIN_R1: + case DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R2: + heatDisinfectState = handleHeatDisinfectMixDrainR2State(); break; - case DG_HEAT_DISINFECT_STATE_RINSE_CIRCULATION: + 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; default: @@ -195,6 +376,8 @@ break; } + publishHeatDisinfectData(); + return heatDisinfectState; } @@ -214,53 +397,82 @@ /*********************************************************************//** * @brief * The stopDGHeatDisinfect function stops heat disinfect mode. - * @details Inputs: heatDisinfectionState - * @details Outputs: heatDisinfectionState - * @return none + * @details Inputs: none + * @details Outputs: none + * @return TRUE is current operation mode is heat disinfect, otherwise FALSE *************************************************************************/ -void stopDGHeatDisinfect( void ) +BOOL stopDGHeatDisinfect( void ) { - heatDisinfectState = DG_HEAT_DISINFECT_STATE_COMPLETE; + BOOL status = FALSE; - requestNewOperationMode( DG_MODE_STAN ); + // Check if the current operation mode is heat disinfect + if ( DG_MODE_HEAT == getCurrentOperationMode() ) + { + // Reset all the actuators + deenergizeActuators(); + + // Transition to mode standby + requestNewOperationMode( DG_MODE_STAN ); + + status = TRUE; + } + + return status; } -// ********** private function prototypes ********** +// ********** private functions ********** /*********************************************************************//** * @brief * The handleHeatDisinfectStartState function handles the heat disinfect - * start state. - * @details Inputs: none - * @details Outputs: none + * 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; + F32 ppiPressure = getMeasuredDGPressure( PRESSURE_SENSOR_RO_PUMP_INLET ); + F32 TDiTemp = getTemperatureValue( TEMPSENSORS_INLET_DIALYSATE ); + F32 TRoTemp = getTemperatureValue( TEMPSENSORS_OUTLET_REDUNDANT ); - // Set all the actuators to reset and deenergized state - resetActuators(); + // Start overall heat disinfect timer + overallHeatDisinfectTimer = getMSTimerCount(); - F32 ppiPressure = getMeasuredDGPressure( PRESSURE_SENSOR_RO_PUMP_INLET ); - F32 TDiTemp = getTemperatureValue( TEMPSENSORS_INLET_DIALYSATE ); - F32 TRoTemp = getTemperatureValue( TEMPSENSORS_OUTLET_REDUNDANT ); + // Set all the actuators to reset and de-energized state + deenergizeActuators(); - // If the inlet pressure is less than the threshold or TDi and TRo difference is greater than 1 C, the cycle + // If the inlet pressure is less than or equal to the threshold or TDi and TRo difference is greater than 3 C, the cycle // should be canceled - if ( ppiPressure < MIN_INLET_PRESSURE_PSI && fabs( TDiTemp - TRoTemp ) > MAX_START_STATE_TEMP_SENSORS_DIFF_C ) + if ( ( ppiPressure <= MIN_INLET_PRESSURE_PSI ) || ( fabs( TDiTemp - TRoTemp ) > MAX_START_STATE_TEMP_SENSORS_DIFF_C ) ) { - setModeToFailed( state ); + prevHeatDisinfectState = state; + alarmDetectedPendingTrigger = ALARM_ID_DG_HEAT_DISINFECT_INLET_PRES_AND_TEMP_SNSRS_OUT; + state = DG_HEAT_DISINFECT_STATE_CANCEL_BASIC_PATH; } else { + // Set the heat disinfect UI state + heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_FLUSH_BEFORE_DISINFECT; + // Close VPi to prevent wasting water setValveState( VPI, VALVE_STATE_CLOSED ); + setValveState( VPO, VALVE_STATE_FILL_C_TO_NC); // Set the actuators to drain R1 - setValveState( VRD, VALVE_STATE_R1_C_TO_NC ); + setValveState( VRD1, VALVE_STATE_OPEN ); setDrainPumpTargetRPM( DRAIN_PUMP_TARGET_RPM ); - stateTimer = getMSTimerCount(); + + rsrvrFillStableTimeCounter = 0; + // Assume reservoir 1 is full and drain it + rsrvr1Status = DG_RESERVOIR_ABOVE_TARGET; + stateTimer = getMSTimerCount(); } return state; @@ -269,28 +481,45 @@ /*********************************************************************//** * @brief * The handleHeatDisinfectDrainR1State function handles the heat disinfect - * drain R1 state. - * @details Inputs: stateTimer - * @details Outputs: stateTimer + * 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; - F32 R1Weight = getLoadCellFilteredWeight( LOAD_CELL_A1 ); - - if ( R1Weight < EMPTY_RESERVOIRS_WEIGHT_GRAMS ) + if ( DG_RESERVOIR_ABOVE_TARGET == rsrvr1Status ) { + rsrvr1Status = getRsrvrDrainStatus( DG_RESERVOIR_1, DRAIN_WEIGHT_UNCHANGE_TIMEOUT, RSRVRS_INITIAL_DRAIN_TIME_OUT_MS ); + } + else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr1Status ) + { + tareLoadCell( LOAD_CELL_RESERVOIR_1_PRIMARY ); + tareLoadCell( LOAD_CELL_RESERVOIR_1_BACKUP ); + + // Assume reservoir 2 is full and drain it + rsrvr2Status = DG_RESERVOIR_ABOVE_TARGET; + + // 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 - setValveState( VRD, VALVE_STATE_R2_C_TO_NO ); - stateTimer = getMSTimerCount(); + // NOTE: Drain pump is already on and VDr is already on drain state + setValveState( VRD2, VALVE_STATE_OPEN ); state = DG_HEAT_DISINFECT_STATE_DRAIN_R2; + + setValveState( VPO, VALVE_STATE_FILL_C_TO_NC); + + // Start the timer + stateTimer = getMSTimerCount(); } - else if ( didTimeout( stateTimer, DRAIN_RESERVOIRS_TIME_OUT_MS ) ) + else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr1Status ) { - setModeToFailed( state ); + prevHeatDisinfectState = state; + state = DG_HEAT_DISINFECT_STATE_CANCEL_BASIC_PATH; } return state; @@ -299,74 +528,103 @@ /*********************************************************************//** * @brief * The handleHeatDisinfectDrainR2State function handles the heat disinfect - * drain R2 state. - * @details Inputs: stateTimer - * @details Outputs: stateTimer + * 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; - F32 R2Weight = getLoadCellFilteredWeight( LOAD_CELL_A2 ); - - if ( R2Weight < EMPTY_RESERVOIRS_WEIGHT_GRAMS ) + if ( DG_RESERVOIR_ABOVE_TARGET == rsrvr2Status ) { - if ( isThisLastDrain ) - { - //TODO set the rest of the actuators - state = DG_HEAT_DISINFECT_STATE_RINSE_CIRCULATION; - } - else - { - stateTrialCounter = 0; - setValveState( VPI, VALVE_STATE_OPEN ); - stateTimer = getMSTimerCount(); - state = DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN; - } + rsrvr2Status = getRsrvrDrainStatus( DG_RESERVOIR_2, DRAIN_WEIGHT_UNCHANGE_TIMEOUT, RSRVRS_INITIAL_DRAIN_TIME_OUT_MS ); } - else if ( didTimeout( stateTimer, DRAIN_RESERVOIRS_TIME_OUT_MS ) ) + else if ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) { - setModeToFailed( state ); + tareLoadCell( LOAD_CELL_RESERVOIR_2_PRIMARY ); + tareLoadCell( LOAD_CELL_RESERVOIR_2_BACKUP ); + + signalDrainPumpHardStop(); + + // Done with draining R2, close it + setValveState( VRD2, VALVE_STATE_CLOSED ); + setValveState( VPI, VALVE_STATE_OPEN ); + stateTrialCounter = 0; + stateTimer = getMSTimerCount(); + state = DG_HEAT_DISINFECT_STATE_FLUSH_DRAIN; + + setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); } + else if ( DG_RESERVOIR_NOT_REACHED_TARGET == rsrvr2Status ) + { + prevHeatDisinfectState = state; + state = DG_HEAT_DISINFECT_STATE_CANCEL_BASIC_PATH; + } return state; } /*********************************************************************//** * @brief * The handleHeatDisinfectFlushDrainState function handles the heat disinfect - * flush drain state. - * @details Inputs: stateTimer, stateTrialCounter - * @details Outputs: stateTimer, stateTrialCounter + * 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 ( didTimeout( stateTimer, FLUSH_DRAIN_WAIT_TIME_MS ) ) + if ( TRUE == didTimeout( stateTimer, FLUSH_DRAIN_WAIT_TIME_MS ) ) { + BOOL hasConductivityPassed = FALSE; + // If the inlet temperature and conductivity are in range, move onto the next state - if ( getTemperatureValue( TEMPSENSORS_INLET_PRIMARY_HEATER ) > MIN_INLET_TEMPERATURE_C && - getConductivityValue( CONDUCTIVITYSENSORS_CPI_SENSOR ) > MIN_INLET_CONDUCTIVITY_S_PER_CM ) + if ( ( getTemperatureValue( TEMPSENSORS_INLET_PRIMARY_HEATER ) >= MIN_INLET_TEMPERATURE_C ) && + ( getConductivityValue( CONDUCTIVITYSENSORS_CPI_SENSOR ) <= MAX_INLET_CONDUCTIVITY_US_PER_CM ) ) { - setValveState( VPD, VALVE_STATE_DRAIN_C_TO_NC ); - setROPumpTargetFlowRate( RO_PUMP_TARGET_FLOW_RATE_LPM, MAX_RO_PUMP_PRESSURE_PSI ); + hasConductivityPassed = TRUE; + } + +#ifndef _RELEASE_ + if ( SW_CONFIG_ENABLE_VALUE == getSoftwareConfigStatus( SW_CONFIG_DISABLE_DISINFECT_CONDUCTIVITY_CHECK ) ) + { + hasConductivityPassed = TRUE; + } +#endif + + if ( TRUE == hasConductivityPassed ) + { + 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; } - // If the failure is still in range, reset the timer and start over - else if ( ++stateTrialCounter < MAX_ALLOWED_STATE_TRIALS ) + // If the number of failures have not exceeded the limit, try again. + else if ( stateTrialCounter < MAX_ALLOWED_STATE_TRIALS ) { + stateTrialCounter++; stateTimer = getMSTimerCount(); } - // Couldn't get a good water sample after a couple of trials and the disinfection cycle failed + // Couldn't get a good water sample after a couple of trials and the disinfect cycle failed else { - setModeToFailed( state ); + alarmDetectedPendingTrigger = ALARM_ID_DG_HEAT_DISINFECT_INLET_COND_OR_TEMP_OUT; + prevHeatDisinfectState = state; + state = DG_HEAT_DISINFECT_STATE_CANCEL_BASIC_PATH; } } @@ -376,31 +634,38 @@ /*********************************************************************//** * @brief * The handleHeatDisinfectFlushCirculationState function handles the heat - * disinfect flush circulation state. - * @details Inputs: stateTimer, stateTrialCounter - * @details Outputs: stateTimer, stateTrialCounter + * 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; // Check if the flush circulation time has elapsed and the temperature sensors are not in range yet - if ( didTimeout( stateTimer, FLUSH_CICRCULATION_WAIT_TIME_MS ) && FALSE == areTempSensorsInRange ) + if ( ( TRUE == didTimeout( stateTimer, FLUSH_CICRCULATION_WAIT_TIME_MS ) ) && ( FALSE == areTempSensorsInRange ) ) { - F32 TPmTemp = 0; //TODO add TPm later. This is the new temp sensor of the coldest spot + F32 ThdTemp = getTemperatureValue( TEMPSENSORS_HEAT_DISINFECT ); F32 TPoTemp = getTemperatureValue( TEMPSENSORS_OUTLET_PRIMARY_HEATER ); F32 TD1Temp = getTemperatureValue( TEMPSENSORS_CONDUCTIVITY_SENSOR_1 ); F32 TD2Temp = getTemperatureValue( TEMPSENSORS_CONDUCTIVITY_SENSOR_2 ); - F32 avgTemp = ( TPmTemp + TPoTemp + TD1Temp + TD2Temp ) / 3.0; //TODO change the number to 4 once the new sensor has been added + F32 avgTemp = ( ThdTemp + TPoTemp + TD1Temp + TD2Temp ) / NUM_OF_TEMP_SENSORS_TO_AVG; - BOOL isTPmOut = FALSE; //TODO change this calculations once TPm is added - BOOL isTPoOut = fabs( TPoTemp - avgTemp ) > MAX_FLUSH_CIRC_TEMP_SENSOR_DIFF_C; - BOOL isTD1Out = fabs( TD1Temp - avgTemp ) > MAX_FLUSH_CIRC_TEMP_SENSOR_DIFF_C; - BOOL isTD2Out = fabs( TD2Temp - avgTemp ) > MAX_FLUSH_CIRC_TEMP_SENSOR_DIFF_C; + // 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 isTD1Out = ( fabs( TD1Temp - 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( isTPmOut || isTPoOut || isTD1Out || isTD2Out ) + if ( ( TRUE == isThdOut ) || ( TRUE == isTPoOut ) || ( TRUE == isTD1Out ) || ( TRUE == isTD2Out ) ) { // Check if we have exceeded the number of trials. If not, try another time if ( ++stateTrialCounter < MAX_ALLOWED_STATE_TRIALS ) @@ -410,90 +675,1271 @@ // State failed. Cancel heat disinfect mode else { - setModeToFailed( state ); + alarmDetectedPendingTrigger = ALARM_ID_DG_TEMP_SENSORS_DIFF_OUT_OF_RANGE; + prevHeatDisinfectState = state; + state = DG_HEAT_DISINFECT_STATE_CANCEL_BASIC_PATH; } } else { areTempSensorsInRange = TRUE; - stateTimer = getMSTimerCount(); - // TODO Turn on the composite pumps and wait for 30 seconds + // 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 ); + + // 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 ( areTempSensorsInRange ) + if ( TRUE == areTempSensorsInRange ) { - // TODO: enable the timeout once the concentrate pumps are available. - //if ( didTimeout( stateTimer, FLUSH_CICRCULATION_WAIT_TIME_MS ) ) - if ( TRUE ) + if ( TRUE == didTimeout( concentratePumpsPrimeTimer, FLUSH_CICRCULATION_WAIT_TIME_MS ) ) { + rsrvr1Status = DG_RESERVOIR_BELOW_TARGET; + rsrvr2Status = DG_RESERVOIR_BELOW_TARGET; - state = DG_HEAT_DISINFECT_STATE_COMPLETE; + // Done with flushing the concentrate pumps line + requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID ); + requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB ); + + setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); + setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); + setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); + 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 ) + { + rsrvr1Status = getRsrvrFillStatus( DG_RESERVOIR_1, RSRVRS_FULL_VOL_ML, RSRVRS_FILL_UP_TIMEOUT_MS ); + + // Keep monitoring the status of reservoir 2 at the same time + rsrvr2Status = getRsrvrFillStatus( DG_RESERVOIR_2, RSRVRS_PARTIAL_FILL_VOL_ML, RSRVRS_500ML_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 ( DG_RESERVOIR_REACHED_TARGET == rsrvr2Status ) + { + 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 resetActuators function sets all the actuators to reset and - * deenergized state. + * 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, RSRVRS_INITIAL_DRAIN_TIME_OUT_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_BASIC_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(); + + // Set the valves to drain R2 and no fill + setValveState( VPI, VALVE_STATE_CLOSED ); + setValveState( VPD, VALVE_STATE_OPEN_C_TO_NC ); + setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); + 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, RSRVRS_INITIAL_DRAIN_TIME_OUT_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_BASIC_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, RSRVRS_INITIAL_DRAIN_TIME_OUT_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_OPEN ); + + // Turn on the RO pump + setROPumpTargetFlowRateLPM( RO_PUMP_TARGET_FLUSH_FILL_FLOW_RATE_LPM, MAX_RO_PUMP_FLUSH_FILL_PRESSURE_PSI ); + + // Start heating the water while we are filling up the reservoirs + setHeaterTargetTemperature( DG_PRIMARY_HEATER, HEAT_DISINFECT_TARGET_TEMPERATURE_C ); + startHeater( DG_PRIMARY_HEATER ); + + rsrvr1Status = DG_RESERVOIR_BELOW_TARGET; + rsrvr2Status = DG_RESERVOIR_BELOW_TARGET; + + // Start the timer for drain timeout + 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_BASIC_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; + + // Set the heat disinfect UI state + 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 ) + { + // 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 ); + + // Set the drain pump to control mode + setDrainPumpTargetOutletPressure( HEAT_DISINFECT_TARGET_DRAIN_PRES_PSI ); + + // Set the RO flow to maximum pressure of 30psi since it is the maximum pressure on the RO filter + // at inlet temperature > 45 C + setROPumpTargetFlowRateLPM( HEAT_DISINFECT_TARGET_RO_FLOW_LPM, HEAT_DISINFECT_MAX_RO_PRESSURE_PSI ); + + // Start the trimmer heater since we are recirculating water and there is flow in the shunt line + setHeaterTargetTemperature( DG_TRIMMER_HEATER, HEAT_DISINFECT_TARGET_TEMPERATURE_C ); + startHeater( DG_TRIMMER_HEATER ); + + // 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 + R1HeatDisinfectVol = getLoadCellLargeFilteredWeight( LOAD_CELL_RESERVOIR_1_PRIMARY ); + R2HeatDisinfectVol = getLoadCellLargeFilteredWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); + + 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(); + + 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 ); + requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB ); + // 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 ); + + // Set the RO flow to maximum pressure of 30psi since it is the maximum pressure on the RO filter + // at inlet temperature > 45 C + setROPumpTargetFlowRateLPM( HEAT_DISINFECT_TARGET_RO_FLOW_TRANSFER_LPM, HEAT_DISINFECT_MAX_RO_PRESSURE_PSI ); + + // Set the drain pump to control mode + setDrainPumpTargetOutletPressure( HEAT_DISINFECT_TARGET_DRAIN_FILL_R2_PSI ); + + // Turn off trimmer heater for transition + stopHeater(DG_TRIMMER_HEATER); + + // 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(); + // Reset the timer for the next disinfect state + tempGradOutOfRangeTimer = 0; + state = DG_HEAT_DISINFECT_STATE_FILL_R2_WITH_HOT_WATER; + break; + + case HEAT_DISINFECT_HEAT_UP_IN_PROGRESS: + default: + heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_HEAT_UP_WATER; + break; + } + + 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; + + // Set the heat disinfect UI state + 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 ) + { + // Get the current volumes to be monitored during R2 to R1 heat disinfect state + R1HeatDisinfectVol = getLoadCellLargeFilteredWeight( LOAD_CELL_RESERVOIR_1_PRIMARY ); + R2HeatDisinfectVol = getLoadCellLargeFilteredWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ); + + // Set the drain pump to control mode + setDrainPumpTargetOutletPressure( HEAT_DISINFECT_TARGET_DRAIN_PRES_PSI ); + + // Set the RO flow to maximum pressure of 30psi since it is the maximum pressure on the RO filter + // at inlet temperature > 45 C + setROPumpTargetFlowRateLPM( HEAT_DISINFECT_TARGET_RO_FLOW_LPM, HEAT_DISINFECT_MAX_RO_PRESSURE_PSI ); + + // Start the trimmer heater since we are recirculating water and there is flow in the shunt line + setHeaterTargetTemperature( DG_TRIMMER_HEATER, HEAT_DISINFECT_TARGET_TEMPERATURE_C ); + startHeater( DG_TRIMMER_HEATER ); + + 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; + + 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: + // Turn off the heaters + stopHeater( DG_PRIMARY_HEATER ); + stopHeater( DG_TRIMMER_HEATER ); + stateTimer = getMSTimerCount(); + state = DG_HEAT_DISINFECT_STATE_COOL_DOWN_HEATERS; + + // Set the disinfect flags + setDisinfectStatus( TRUE ); + setLastDisinfectDate( USAGE_INFO_HEAT_DISINFECT, getRTCTimestamp() ); + 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; + + // Set the heat disinfect UI state + heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_COOL_DOWN_DEVICE; + + writeDisinfectDataToNV(); + + if ( TRUE == didTimeout( stateTimer, POST_HEAT_DISINFECT_WAIT_TIME_MS ) ) + { + // Stop the drain pump and the RO pump to exit the closed loop + signalDrainPumpHardStop(); + signalROPumpHardStop(); + + // De-energize all the valves that are not in the path anymore + // and wait for the RO membrane to be cooled down. + setValveState( VPI, VALVE_STATE_CLOSED ); + setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); + setValveState( VRD1, VALVE_STATE_CLOSED ); + + setValveState( VBF, VALVE_STATE_OPEN ); + setValveState( VDR, VALVE_STATE_RECIRC_C_TO_NC ); + setValveState( VRC, VALVE_STATE_RECIRC_C_TO_NC ); + setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); + + setROPumpTargetFlowRateLPM( ROF_COOL_DOWN_TARGET_FLOW_LPM, HEAT_DISINFECT_MAX_RO_PRESSURE_PSI ); + + ROFCoolingTimer = 0; + stateTimer = getMSTimerCount(); + state = DG_HEAT_DISINFECT_STATE_COOL_DOWN_RO_FILTER; + } + + return state; +} + +/*********************************************************************//** + * @brief + * The handleHeatDisinfectCoolDownROFilterState function handles the heat + * disinfect cool down RO filter state. The state monitors the temperature + * at THd and if it is less than 45 C, it transitions to the next state. + * @details Inputs: stateTimer, ROFCoolingTimer + * @details Outputs: stateTimer, ROFcoolingTimer, heatDisinfectUIState + * @return next state of the heat disinfect state machine + *************************************************************************/ +static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCoolDownROFilterState( void ) +{ + DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_COOL_DOWN_RO_FILTER; + // THd is the closet sensor to the RO filter and this temperature is monitored + // until it is dropped below 45 C to be able to run fluid through the RO filter + F32 THdTemp = getTemperatureValue( TEMPSENSORS_HEAT_DISINFECT ); + + // Set the heat disinfect UI state + heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_COOL_DOWN_DEVICE; + + if ( ( 0 == ROFCoolingTimer ) && ( THdTemp < TARGET_THD_SENSOR_FOR_RINSING_C ) ) + { + // Temperature is below target - perform mandatory cool down + ROFCoolingTimer = getMSTimerCount(); + } + else if ( THdTemp >= TARGET_THD_SENSOR_FOR_RINSING_C ) + { + // Temperature is not below target - reset the timer and keep looking + ROFCoolingTimer = 0; + } + + if ( ( 0 != ROFCoolingTimer ) && ( TRUE == didTimeout( ROFCoolingTimer, ROF_COOL_DOWN_CIRCULATION_TIME_MS ) ) ) + { + // Temperature is below target, transition to next state + setValveState( VPI, VALVE_STATE_OPEN ); + setValveState( VBF, VALVE_STATE_CLOSED ); + setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); + setValveState( VRC, VALVE_STATE_RECIRC_C_TO_NC ); + setValveState( VDR, VALVE_STATE_DRAIN_C_TO_NO ); + setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); + setValveState( VRI, VALVE_STATE_R1_C_TO_NO ); + setValveState( VRF, VALVE_STATE_R2_C_TO_NO ); + + signalROPumpHardStop(); + + rsrvr1Status = DG_RESERVOIR_ABOVE_TARGET; + stateTimer = getMSTimerCount(); + state = DG_HEAT_DISINFECT_STATE_MIX_DRAIN_R1; + } + else if ( ( TRUE == didTimeout( stateTimer, ROF_COOL_DOWN_MAX_TIME_MS ) ) ) + { + prevHeatDisinfectState = state; + alarmDetectedPendingTrigger = ALARM_ID_DG_HEAT_DISINFECT_TARGET_TEMP_TIMEOUT; // TODO - cool down alarm? + state = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; + } + + 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; + + // Set the heat disinfect UI state + 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( VRD1, VALVE_STATE_OPEN ); + // Turn on the drain pump to drain the reservoirs in open loop mode + setDrainPumpTargetRPM( DRAIN_PUMP_RPM_IN_MIX_DRAIN ); + } + 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_BASIC_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_BASIC_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; + + // Set the cancellation mode + 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 + * @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; + + // Set the heat disinfect UI state + heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_CANCEL_DISINFECT; + U32 drainTimeoutMS = RSRVRS_INITIAL_DRAIN_TIME_OUT_MS; + + 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(); + + // 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; + drainTimeoutMS = RSRVRS_MIX_DRAIN_TIMEOUT_MS; + } + + rsrvr1Status = DG_RESERVOIR_ABOVE_TARGET; + rsrvr2Status = DG_RESERVOIR_ABOVE_TARGET; + + // 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 ); + // Start the drain pump + setDrainPumpTargetRPM( targetRPM ); + + // Start the timer for drain timeout + stateTimer = getMSTimerCount(); + } + + // 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, 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, 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 none + * @return next state of the heat disinfect state machine *************************************************************************/ -static void resetActuators( void ) +static DG_HEAT_DISINFECT_STATE_T handleHeatDisinfectCompleteState( void ) { - // UV reactors will not be used in the heat disinfection since their operating temperature - // range is below 85C and they might be damaged by the high temperature. - turnOffUVReactor( INLET_UV_REACTOR ); - turnOffUVReactor( OUTLET_UV_REACTOR ); + DG_HEAT_DISINFECT_STATE_T state = DG_HEAT_DISINFECT_STATE_COMPLETE; - // Deenergize all the valves - setValveState( VPI, VALVE_STATE_OPEN ); - setValveState( VBF, VALVE_STATE_CLOSED ); - setValveState( VSP, VALVE_STATE_CLOSED ); - setValveState( VPD, VALVE_STATE_OPEN_C_TO_NO ); - setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); - setValveState( VDR, VALVE_STATE_DRAIN_C_TO_NO ); - setValveState( VRC, VALVE_STATE_DRAIN_C_TO_NO ); - setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); - setValveState( VRD, VALVE_STATE_R2_C_TO_NO ); - setValveState( VRI, VALVE_STATE_R1_C_TO_NO ); - setValveState( VRF, VALVE_STATE_R2_C_TO_NO ); + // Set the heat disinfect UI state + heatDisinfectUIState = HEAT_DISINFECT_UI_STATE_COMPLETE; - //TODO add the composition pumps - signalROPumpHardStop(); - signalDrainPumpHardStop(); - stopPrimaryHeater(); - stopTrimmerHeater(); + stopDGHeatDisinfect(); + + return state; } /*********************************************************************//** * @brief - * The setModeToFailed function sets the heat disinfect mode to failed - * by changing the state to complete and calling another function to set - * the actuators to reset state. - * @details Inputs: stateTrialCounter, stateTimer, areTempSensorsInRange, - * heatDisinfectState TODO add more variables if needed + * The failHeatDisinfect function sets the alarm that failed the heat + * disinfect mode. + * @details Inputs: alarmDetectedPendingTrigger, prevHeatDisinfectState * @details Outputs: none * @return none *************************************************************************/ -static void setModeToFailed( DG_HEAT_DISINFECT_STATE_T failedState ) +static void failHeatDisinfect( void ) { - // Reset all the variables - stateTrialCounter = 0; - stateTimer = 0; - areTempSensorsInRange = FALSE; - heatDisinfectState = DG_HEAT_DISINFECT_STATE_COMPLETE; + SET_ALARM_WITH_1_U32_DATA( alarmDetectedPendingTrigger, prevHeatDisinfectState ) +} - // Reset the actuators before alarming - resetActuators(); +/*********************************************************************//** + * @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.0; - SET_ALARM_WITH_1_U32_DATA( ALARM_ID_DG_HEAT_DISINFECT_CYCLE_FAILED, failedState ) + 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; + + // If the drain parameters of the reservoir is not initialized, initialize them + if ( FALSE == haveDrainParamsBeenInit[ r ] ) + { + initDrainParameters( r ); + haveDrainParamsBeenInit[ r ] = TRUE; + } + + BOOL isDrainComplete = hasTargetDrainVolumeBeenReached( 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, + * targetDisinfectTime + * @details Outputs: tempGradOutOfRangeTimer, alarmDetectedPendingTrigger, + * areRsrvrsLeaking, isPartialDisinfectInProgress, + * heatDisinfectTimer, targetDisinfectTime + * @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 TPoTemp = getTemperatureValue( TEMPSENSORS_OUTLET_PRIMARY_HEATER ); + F32 ThdTemp = getTemperatureValue( TEMPSENSORS_HEAT_DISINFECT ); + + BOOL isR1OutOfRange = fabs( getLoadCellSmallFilteredWeight( LOAD_CELL_RESERVOIR_1_PRIMARY ) - R1HeatDisinfectVol ) > RSRVRS_MAX_TARGET_VOL_CHANGE_ML; + BOOL isR2OutOfRange = fabs( getLoadCellSmallFilteredWeight( LOAD_CELL_RESERVOIR_2_PRIMARY ) - R2HeatDisinfectVol ) > RSRVRS_MAX_TARGET_VOL_CHANGE_ML; + + // Check if the temperature gradient in between the coldest and the hottest spot is more than the specified temperature and + // the timer has not started yet, start it + BOOL gradientOutOfRange = ( fabs( TPoTemp - ThdTemp ) > HEAT_DISINFECT_MAX_TEMP_GRADIENT_C ) ? TRUE : FALSE; + + // Perform check if no pending alarm + if (ALARM_ID_NO_ALARM == alarmDetectedPendingTrigger) + { + if ( TRUE != gradientOutOfRange ) + { + tempGradOutOfRangeTimer = 0; + } + else if ( 0 == tempGradOutOfRangeTimer ) + { + 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) + { + // 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; + } + } + + // Perform check if no pending alarm + if (ALARM_ID_NO_ALARM == alarmDetectedPendingTrigger) + { + // If the coldest spot which is THd is less than minimum heat disinfect temperature, + // reset the heat disinfect timers and check whether heating up has timed out + if ( ThdTemp < HEAT_DISINFECT_START_TEMPERATURE_C ) + { + // Keep reseting the disinfect timer so the elapsed time is always 0 until disinfect truly starts + heatDisinfectTimer = getMSTimerCount(); + isPartialDisinfectInProgress = FALSE; + targetDisinfectTime = 0; + + if ( TRUE == didTimeout( stateTimer, HEAT_DISINFECT_START_TEMP_TIMOUT_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; + } + } + else if ( ( isPartialDisinfectInProgress != TRUE ) && ( ThdTemp > HEAT_DISINFECT_START_TEMPERATURE_C ) ) + { + // The temperature of the coldest spot is in range to start the disinfect timer + heatDisinfectTimer = getMSTimerCount(); + isPartialDisinfectInProgress = TRUE; + targetDisinfectTime = HEAT_DISINFECT_TIME_MS; + status = HEAT_DISINFECT_DISINFECT_IN_PROGRESS; + + // In disinfect R1 to R2, concentrate pumps are also run + if ( DG_HEAT_DISINFECT_STATE_DISINFECT_R1_TO_R2 == heatDisinfectState ) + { + // 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 ); + } + } + } + + // Perform check if no pending alarm + if (ALARM_ID_NO_ALARM == alarmDetectedPendingTrigger) + { + // If heat disinfect temperature has been reached, check if this stage of heat disinfect is done + if ( ( TRUE == isPartialDisinfectInProgress ) && ( TRUE == didTimeout( heatDisinfectTimer, HEAT_DISINFECT_TIME_MS ) ) ) + { + // Done with this stage of heat disnfect. Reset the variables + // Target disinfect time is the time that is published to the UI and when there is + // no disinfect count down, this variable is set to 0. When the target time is 0, the UI + // hides the timer on the UI disinfect screen + targetDisinfectTime = 0; + status = HEAT_DISINFECT_COMPLETE; + isPartialDisinfectInProgress = FALSE; + } + } + + 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; + + // If the mode is in the actual heat disinfect states, publish the elapsed time, otherwise publish 0 to avoid confusion + if ( ( DG_HEAT_DISINFECT_STATE_DISINFECT_R1_TO_R2 == heatDisinfectState ) || + ( DG_HEAT_DISINFECT_STATE_DISINFECT_R2_TO_R1 == heatDisinfectState ) ) + { + // If the disinfect target time is 0, meaning the actual disinfect has not started, set the count down to 0, otherwise, publish + // the actual value + U32 countDown = ( HEAT_DISINFECT_TIME_MS == targetDisinfectTime ? ( targetDisinfectTime - calcTimeSince( heatDisinfectTimer) ) : 0 ); + + uiData.heatDisinfectTargetTime = targetDisinfectTime; + uiData.heatDisinfectCountdownTime = countDown / 1000; // The count down is converted into seconds since the UI does not work with milliseconds + data.R1FillLevel = R1HeatDisinfectVol; + data.R2FillLevel = R2HeatDisinfectVol; + } + else + { + uiData.heatDisinfectTargetTime = 0; + uiData.heatDisinfectCountdownTime = 0; + data.R1FillLevel = 0.0; + data.R2FillLevel = 0.0; + } + + broadcastData( MSG_ID_DG_HEAT_DISINFECT_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&data, sizeof( MODE_HEAT_DISINFECT_DATA_T ) ); + + // Publish data to UI + 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 ) +{ +#ifndef _RELEASE_ + if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_CAPS_MONITOR ) != SW_CONFIG_ENABLE_VALUE ) +#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 + U32 cap = (U32)( STATE_OPEN == getSwitchStatus( CONCENTRATE_CAP ) ? CONCENTRATE_CAP : DIALYSATE_CAP ); + prevHeatDisinfectState = heatDisinfectState; + heatDisinfectState = DG_HEAT_DISINFECT_STATE_CANCEL_WATER_PATH; + alarmDetectedPendingTrigger = ALARM_ID_DG_DIALYSATE_OR_CONC_CAP_NOT_IN_PROPER_POSITION; + } + } +} + +/*********************************************************************//** + * @brief + * The writeDisinfectDataToNV function writes the disinfection data to the + * non-volatile memory. + * @details Inputs: disinfectNVOps + * @details Outputs: disinfectNVOps + * @return: none + *************************************************************************/ +static void writeDisinfectDataToNV( void ) +{ + if ( FALSE == disinfectNVOps.hasDisCompleteDateBeenWrittenToNV ) + { + disinfectNVOps.hasDisCompleteDateBeenWrittenToNV = setDisinfectStatus( TRUE ); + } + + if ( FALSE == disinfectNVOps.hasDisStatusBeenWrittenToNV ) + { + disinfectNVOps.hasDisStatusBeenWrittenToNV = setLastDisinfectDate( USAGE_INFO_HEAT_DISINFECT, getRTCTimestamp() ); + } +} + /**@}*/