/************************************************************************** * * Copyright (c) 2021-2025 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 ModeGenIdle.c * * @author (last) Dara Navaei * @date (last) 09-Apr-2024 * * @author (original) Quang Nguyen * @date (original) 06-Aug-2021 * ***************************************************************************/ #include "ConcentratePumps.h" #include "ConductivitySensors.h" #include "DrainPump.h" #include "FPGA.h" #include "CPLD.h" #include "HDDefs.h" #include "Heaters.h" #include "ModeFill.h" #include "ModeGenIdle.h" #include "NVDataMgmt.h" #include "OperationModes.h" #include "Pressures.h" #include "Reservoirs.h" #include "ROPump.h" #include "SystemComm.h" #include "SystemCommMessages.h" #include "TaskGeneral.h" #include "TemperatureSensors.h" #include "Timers.h" #include "UVReactors.h" #include "Valves.h" /** * @addtogroup DGGenIdleMode * @{ */ // ********** private definitions ********** #define TARGET_RO_PRESSURE_PSI 130 ///< Target pressure for RO pump. #define TARGET_RO_FLOW_RATE_L 0.8F ///< Target flow rate for RO pump. #define MAX_IDLE_RSVR_WEIGHT_GAIN_ML 200.0F ///< Maximum fluid gain of inactive reservoir in Gen Idle mode (mL) #define HD_LOST_COMM_TIMEOUT_MS (5 * SEC_PER_MIN * MS_PER_SECOND ) ///< The time of HD lost comm before DG transition back to standby. #define BAD_FILL_SUBSTATES_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Interval (ms/task time) at which the bad fill sub-states is published on the CAN bus. #define DATA_PUBLISH_COUNTER_START_COUNT 61 ///< Data publish counter start count. // ********** private data ********** static U32 dataPublishCounter; ///< Used to schedule gen idle data publication to CAN bus. static DG_GEN_IDLE_MODE_STATE_T genIdleState; ///< Currently active generation idle state. // NOTE: the bad fill state must be initialized here and not in the transition function since in case of a bad fill, the transition function is called // several times to drain and fill and handle a bad fill. static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T badFillState = DG_HANDLE_BAD_FILL_STATE_START; ///< Initialize bad fill sub-state. static U32 hdLostCommStartTime_ms; ///< Lost communication with HD start time in ms. static BOOL handleBadFillFlag; ///< Internal signal flag to handle bad fill. static OVERRIDE_U32_T genIdleDataPublicationInterval = { BAD_FILL_SUBSTATES_PUB_INTERVAL, BAD_FILL_SUBSTATES_PUB_INTERVAL, 0, 0 }; ///< Interval (in ms) at which to publish bad fill sub-states to CAN bus. static F32 initialReservoirWeight; ///< Initial weight of inactive reservoir in Gen Idle mode. static DG_RESERVOIR_ID_T inactiveReservoir; ///< Inactive reservoir // ********** private function prototypes ********** static DG_GEN_IDLE_MODE_STATE_T handleIdleStartState( void ); static DG_GEN_IDLE_MODE_STATE_T handleFlushWaterState( void ); static DG_GEN_IDLE_MODE_STATE_T handleBadFillState( void ); static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T handleStartState( void ); static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T handleFirstDrainState( void ); static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T handleFlushFillState( void ); static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T handleSecondDrainState( void ); static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T handleRefillState( DG_GEN_IDLE_MODE_STATE_T* idleState ); static void checkInvalidReservoirFill( void ); static void publishGenIdleSubstates( void ); /*********************************************************************//** * @brief * The initGenIdleMode function initializes the generation idle mode module. * @details Inputs: none * @details Outputs: Generation idle mode module initialized * @return none *************************************************************************/ void initGenIdleMode( void ) { genIdleState = DG_GEN_IDLE_MODE_STATE_START; hdLostCommStartTime_ms = 0; } /*********************************************************************//** * @brief * The transitionToGenIdleMode function prepares for transition to generation idle mode. * @details Inputs: none * @details Outputs: Re-initialized generation idle mode * @return initial state *************************************************************************/ U32 transitionToGenIdleMode( void ) { BOOL cp1parkStatus = ( getConcPumpIsParked( CONCENTRATEPUMPS_CP1_ACID ) != TRUE ? PARK_CONC_PUMPS : NO_PARK_CONC_PUMPS ); BOOL cp2parkStatus = ( getConcPumpIsParked( CONCENTRATEPUMPS_CP2_BICARB ) != TRUE ? PARK_CONC_PUMPS : NO_PARK_CONC_PUMPS ); // Re-initialize each time we transition to generation idle mode initGenIdleMode(); setCurrentSubState( NO_SUB_STATE ); // Set initial actuator states setValveState( VSP, VALVE_STATE_CLOSED ); setValveState( VPI, VALVE_STATE_OPEN ); setValveState( VPD, VALVE_STATE_OPEN_C_TO_NC ); setValveState( VRD1, VALVE_STATE_CLOSED ); setValveState( VRD2, VALVE_STATE_CLOSED ); setValveState( VRC, VALVE_STATE_DRAIN_C_TO_NO ); setValveState( VDR, VALVE_STATE_DRAIN_C_TO_NO ); setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); // Set back the conductivity of CD2 calibration table to the normal calibration table setCondcutivitySensorCalTable( CONDUCTIVITYSENSORS_CD2_SENSOR, CAL_DATA_CD2_COND_SENSOR ); signalDrainPumpHardStop(); requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID, cp1parkStatus ); requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB, cp2parkStatus ); // UV reactors on turnOnUVReactor( INLET_UV_REACTOR ); turnOnUVReactor( OUTLET_UV_REACTOR ); // NOTE: The target flow rate should be set prior to setting the start primary heater // because the initial guess in the heaters driver needs the target flow to calculate // the new PWMs for the main and small primary heaters setROPumpTargetFlowRateLPM( TARGET_RO_FLOW_RATE_L, TARGET_RO_PRESSURE_PSI ); if ( FALSE == isHeaterOn( DG_PRIMARY_HEATER ) ) { setHeaterTargetTemperature( DG_PRIMARY_HEATER, getPrimaryHeaterTargetTemperature() ); startHeater( DG_PRIMARY_HEATER ); } setCPLDCleanLEDColor( CPLD_CLEAN_LED_OFF ); inactiveReservoir = getInactiveReservoir(); initialReservoirWeight = getReservoirWeight( inactiveReservoir ); return genIdleState; } /*********************************************************************//** * @brief * The getCurrentGenIdleState function returns the current state of the * generation idle mode. * @details Inputs: genIdleState * @details Outputs: none * @return the current state of generation idle mode *************************************************************************/ DG_GEN_IDLE_MODE_STATE_T getCurrentGenIdleState( void ) { return genIdleState; } /*********************************************************************//** * @brief * The getCurrentGenIdleBadFillState function returns the current state of the * generation idle mode bad fill. * @details Inputs: badFillState * @details Outputs: none * @return the current state of generation idle mode bad fill *************************************************************************/ DG_GEN_IDLE_MODE_BAD_FILL_STATE_T getCurrentGenIdleBadFillState( void ) { return badFillState; } /*********************************************************************//** * @brief * The requestDGStop function handles an HD request to stop (return to standby mode). * @details Inputs: none * @details Outputs: DG standby mode requested * @return TRUE if request accepted, FALSE if not. *************************************************************************/ BOOL requestDGStop( void ) { BOOL result = TRUE; requestNewOperationMode( DG_MODE_STAN ); return result; } /*********************************************************************//** * @brief * The setBadAvgConductivityDetectedFlag function sets a flag to indicate * that bad average conductivity is detected. * @details Inputs: none * @details Outputs: handleBadFillFlag, badFillState * @param flag to TRUE if bad avg conductivity otherwise FALSE *************************************************************************/ void setBadAvgConductivityDetectedFlag( BOOL badAvgConducitivyflag ) { handleBadFillFlag = badAvgConducitivyflag; badFillState = DG_HANDLE_BAD_FILL_STATE_START; // Reset bad fill state too so we start at beginning } /*********************************************************************//** * @brief * The execGenIdleMode function executes the generation idle mode state machine. * @details Inputs: genIdleState * @details Outputs: Check water quality, generation idle mode state machine executed * @return current state *************************************************************************/ U32 execGenIdleMode( void ) { // Check inlet water conductivity, temperature, pressure, and RO rejection ratio checkInletWaterConductivity(); checkInletWaterTemperature(); checkInletWaterPressure(); // Transition to standby mode when HD is not communicating if ( TRUE == isHDCommunicating() ) { hdLostCommStartTime_ms = getMSTimerCount(); } else { if ( TRUE == didTimeout( hdLostCommStartTime_ms, HD_LOST_COMM_TIMEOUT_MS ) ) { requestNewOperationMode( DG_MODE_STAN ); } } // Execute current generation idle state switch ( genIdleState ) { case DG_GEN_IDLE_MODE_STATE_START: genIdleState = handleIdleStartState(); break; case DG_GEN_IDLE_MODE_STATE_FLUSH_WATER: genIdleState = handleFlushWaterState(); break; case DG_GEN_IDLE_MODE_STATE_HANDLE_BAD_FILL: genIdleState = handleBadFillState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_GEN_IDLE_MODE_INVALID_EXEC_STATE, genIdleState ) genIdleState = DG_GEN_IDLE_MODE_STATE_START; break; } checkInvalidReservoirFill(); publishGenIdleSubstates(); return (U32)genIdleState; } /*********************************************************************//** * @brief * The handleIdleStartState function executes the start state of the * generation idle mode state machine. * @details Inputs: handleBadFillFlag * @details Outputs: badFillState * @return the next state *************************************************************************/ static DG_GEN_IDLE_MODE_STATE_T handleIdleStartState( void ) { DG_GEN_IDLE_MODE_STATE_T result = DG_GEN_IDLE_MODE_STATE_START; if ( TRUE == handleBadFillFlag ) { result = DG_GEN_IDLE_MODE_STATE_HANDLE_BAD_FILL; } else { badFillState = DG_HANDLE_BAD_FILL_STATE_START; result = DG_GEN_IDLE_MODE_STATE_FLUSH_WATER; } return result; } /*********************************************************************//** * @brief * The handleFlushWaterState function executes the flush water state * generation idle mode state machine. * @details Inputs: none * @details Outputs: none * @return the next state *************************************************************************/ static DG_GEN_IDLE_MODE_STATE_T handleFlushWaterState( void ) { DG_GEN_IDLE_MODE_STATE_T result = DG_GEN_IDLE_MODE_STATE_FLUSH_WATER; return result; } /*********************************************************************//** * @brief * The handleBadFillState function executes the bad fill state of the * generation idle mode state machine. * @details Inputs: none * @details Outputs: badFillState, genIdleState * @return the next state *************************************************************************/ static DG_GEN_IDLE_MODE_STATE_T handleBadFillState( void ) { DG_GEN_IDLE_MODE_STATE_T result = DG_GEN_IDLE_MODE_STATE_HANDLE_BAD_FILL; U32 priorSubState = badFillState; // Execute current bad fill state switch ( badFillState ) { case DG_HANDLE_BAD_FILL_STATE_START: badFillState = handleStartState(); break; case DG_HANDLE_BAD_FILL_STATE_FIRST_DRAIN: badFillState = handleFirstDrainState(); break; case DG_HANDLE_BAD_FILL_STATE_FLUSH_FILL: badFillState = handleFlushFillState(); break; case DG_HANDLE_BAD_FILL_STATE_SECOND_DRAIN: badFillState = handleSecondDrainState(); break; case DG_HANDLE_BAD_FILL_STATE_REFILL: badFillState = handleRefillState( &result ); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_GEN_IDLE_MODE_INVALID_EXEC_STATE, genIdleState ) badFillState = DG_HANDLE_BAD_FILL_STATE_START; break; } if ( priorSubState != badFillState ) { setCurrentSubState( badFillState ); } return result; } /*********************************************************************//** * @brief * The handleStartState function executes the start state of the handle bad * fill state machine. * @details Inputs: none * @details Outputs: none * @return the next state *************************************************************************/ static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T handleStartState( void ) { DG_GEN_IDLE_MODE_BAD_FILL_STATE_T state = DG_HANDLE_BAD_FILL_STATE_FIRST_DRAIN; // Drain the bad filled reservoir first requestNewOperationMode( DG_MODE_DRAI ); return state; } /*********************************************************************//** * @brief * The handleFirstDrainState function executes the first drain state of the * handle bad fill state machine. * @details Inputs: none * @details Outputs: none * @return the next state *************************************************************************/ static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T handleFirstDrainState( void ) { DG_GEN_IDLE_MODE_BAD_FILL_STATE_T state = DG_HANDLE_BAD_FILL_STATE_FIRST_DRAIN; // Check if the alarm has been cleared by the user and if yes, continue with the fill if ( FALSE == isAlarmActive( ALARM_ID_DG_FILL_CONDUCTIVITY_OUT_OF_RANGE ) ) { state = DG_HANDLE_BAD_FILL_STATE_FLUSH_FILL; // Start the flush fill. // NOTE: the actual target fill from HD is sent here but at this stage the fill target is 1000 mL. // The other functions that check against the fill target know to check for 1000 mL. This is to make sure // the actual fill target from HD is not overridden in case multiple bad fills occurred back to back. startFillCmd( getTargetFillVolumeML(), getTargetFillFlowRateLPM() ); } return state; } /*********************************************************************//** * @brief * The handleFlushFillState function executes the flush fill state of the * handle bad fill state machine. * @details Inputs: none * @details Outputs: none * @return the next state *************************************************************************/ static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T handleFlushFillState( void ) { DG_GEN_IDLE_MODE_BAD_FILL_STATE_T state = DG_HANDLE_BAD_FILL_STATE_SECOND_DRAIN; requestNewOperationMode( DG_MODE_DRAI ); return state; } /*********************************************************************//** * @brief * The handleSecondDrainState function executes the second drain state of the * handle bad fill state machine. * @details Inputs: none * @details Outputs: none * @return the next state *************************************************************************/ static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T handleSecondDrainState( void ) { DG_GEN_IDLE_MODE_BAD_FILL_STATE_T state = DG_HANDLE_BAD_FILL_STATE_REFILL; // Refill to the saved target fill volume (~1500 mL) startFillCmd( getTargetFillVolumeML(), getTargetFillFlowRateLPM() ); return state; } /*********************************************************************//** * @brief * The handleRefillState function executes refill state of the handle bad * fill state machine. * @details Inputs: none * @details Outputs: handleBadFillFlag * @param idleState reference variable for the next idle state * @return the next state *************************************************************************/ static DG_GEN_IDLE_MODE_BAD_FILL_STATE_T handleRefillState( DG_GEN_IDLE_MODE_STATE_T* idleState ) { DG_GEN_IDLE_MODE_BAD_FILL_STATE_T state = DG_HANDLE_BAD_FILL_STATE_START; // Clear wait for dialysate alarm condition to allow resume clearAlarmCondition( ALARM_ID_DG_CREATING_DIALYSATE_PLEASE_WAIT ); // resume option will appear // Set flag to FALSE here so next call to idle exec will move to normal flush water state handleBadFillFlag = FALSE; *idleState = DG_GEN_IDLE_MODE_STATE_START; return state; } /*********************************************************************//** * @brief * The checkInvalidReservoirFill function checks for reservoir filling in * generation idle mode when it should be constant. * @details Inputs: inactiveReservoir, initialReservoirWeight * @details Outputs: inactiveReservoir, initialReservoirWeight * @return the next state *************************************************************************/ static void checkInvalidReservoirFill( void ) { DG_RESERVOIR_ID_T currentInactiveReservoir = getInactiveReservoir(); F32 reservoirWeight = getReservoirWeight( currentInactiveReservoir ); if ( currentInactiveReservoir != inactiveReservoir ) { // Inactive Reservoir changed, update the start value inactiveReservoir = currentInactiveReservoir; initialReservoirWeight = getReservoirWeight( inactiveReservoir ); } // Check for unwanted filling unless a transfer is in progress if ( ( FALSE == isReservoirTransferInProgress() ) && ( reservoirWeight > ( initialReservoirWeight + MAX_IDLE_RSVR_WEIGHT_GAIN_ML ) ) ) { SET_ALARM_WITH_2_F32_DATA( ALARM_ID_DG_INACTIVE_RESERVOIR_WEIGHT_OUT_OF_RANGE, initialReservoirWeight, reservoirWeight ); } } /*********************************************************************//** * @brief * The publishGenIdleSubstates function publishes gen idle * sub-states at the set interval. * @details Inputs: handleBadFillFlag, badFillState, targetFillVolumeML, * dataPublishCounter * @details Outputs: dataPublishCounter * @return none *************************************************************************/ static void publishGenIdleSubstates( void ) { // publish Gen Idle pump data on interval if ( ++dataPublishCounter >= getU32OverrideValue( &genIdleDataPublicationInterval ) ) { DG_GEN_IDLE_DATA_T data; // Populate the data structure for publication data.badFillSignal = (U32)handleBadFillFlag; data.badFillState = (U32)badFillState; data.genIdleState = (U32)getCurrentGenIdleState(); data.targetFillVolumemL = getTargetFillVolumeML(); broadcastData( MSG_ID_DG_GEN_IDLE_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&data, sizeof( DG_GEN_IDLE_DATA_T ) ); dataPublishCounter = 0; } } /*********************************************************************//** * @brief * The getCurrentBadFillSignal function returns the current * generation idle mode bad fill flag. * @details Inputs: handleBadFillFlag * @details Outputs: none * @return the current generation idle mode bad fill flag *************************************************************************/ BOOL getCurrentBadFillSignal( void ) { return handleBadFillFlag; } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testSetBadFillSubstatesPublishIntervalOverride function overrides the * bad fill sub-states publish interval. * @details Inputs: badFillSubstatesPublishInterval * @details Outputs: badFillSubstatesPublishInterval * @param value override bad fill sub-states publish interval with (in ms) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetGenIdleSubstatesPublishIntervalOverride( U32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { U32 intvl = value / TASK_GENERAL_INTERVAL; genIdleDataPublicationInterval.ovData = intvl; genIdleDataPublicationInterval.override = OVERRIDE_KEY; result = TRUE; } return result; } /*********************************************************************//** * @brief * The testResetBadFillSubstatesPublishIntervalOverride function resets the * override of the bad fill sub-states publish interval. * @details Inputs: badFillSubstatesPublishInterval * @details Outputs: badFillSubstatesPublishInterval * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetGenIdleSubstatesPublishIntervalOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { genIdleDataPublicationInterval.override = OVERRIDE_RESET; genIdleDataPublicationInterval.ovData = genIdleDataPublicationInterval.ovInitData; result = TRUE; } return result; } /**@}*/