/************************************************************************** * * Copyright (c) 2019-2020 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 ModeFill.c * * @author (last) Quang Nguyen * @date (last) 25-Aug-2020 * * @author (original) Leonardo Baloa * @date (original) 19-Nov-2019 * ***************************************************************************/ #include "ConcentratePumps.h" #include "ConductivitySensors.h" #include "FPGA.h" #include "Heaters.h" #include "LoadCell.h" #include "ModeFill.h" #include "OperationModes.h" #include "PersistentAlarm.h" #include "Pressures.h" #include "Reservoirs.h" #include "ROPump.h" #include "TaskGeneral.h" #include "TemperatureSensors.h" #include "Timers.h" #include "UVReactors.h" #include "Valves.h" /** * @addtogroup DGFillMode * @{ */ // ********** private definitions ********** #define TARGET_RO_PRESSURE_PSI 130 ///< Target pressure for RO pump. #define TARGET_RO_FLOW_RATE_L 0.8 ///< Target flow rate for RO pump. #define DIALYSATE_ACID_CONCENTRATE_RATIO ( 2.35618 / FRACTION_TO_PERCENT_FACTOR ) ///< Ratio between RO water and acid concentrate. #define DIALYSATE_BICARB_CONCENTRATE_RATIO ( 4.06812 / FRACTION_TO_PERCENT_FACTOR ) ///< Ratio between RO water and bicarbonate concentrate. #define DIALYSATE_FILL_TIME_OUT ( 5 * SEC_PER_MIN * MS_PER_SECOND ) ///< Time out period when reservoir is not filled with correct dialysate. #define ACID_BICARB_CONCENTRATE_ADDITION_MULTIPLER 1.06 ///< Acid and bicarbonate concentrates make up around 6% to total volume. #define FLOW_INTEGRATED_VOLUME_CHECK_TOLERANCE 0.1 ///< Flow integrated volume has 10% tolerance compare to load cell reading. #define CONCENTRATE_TEST_TIME_OUT_MS ( 30 * MS_PER_SECOND ) ///< Concentrate test time out period in ms. #define CONDUCTIVITY_ERROR_PERSISTENCE_PERIOD_MS ( 5 * MS_PER_SECOND ) ///< Persistence period for conductivity error. /// Multiplier to conver flow (mL/min) into volume (mL) for period of general task interval. static const F32 RO_FLOW_INTEGRATOR = ( ( ML_PER_LITER * TASK_GENERAL_INTERVAL ) / ( SEC_PER_MIN * MS_PER_SECOND ) ); // ********** private data ********** static DG_FILL_MODE_STATE_T fillState; ///< Currently active fill state. static U32 dialysateFillStartTime; ///< Current time when starting to fill dialysate. static F32 reservoirBaseWeight; ///< Fill reservoir base weight. static F32 totalROFlowRate; ///< Total RO flow rate over period of time. static U32 concentrateTestStartTime; ///< Starting time for concentrate test. // ********** private function prototypes ********** static BOOL isWaterQualityGood( void ); static BOOL checkDialysateConductivity( void ); static DG_FILL_MODE_STATE_T handleCheckInletWaterState( void ); static DG_FILL_MODE_STATE_T handleBicarbPumpCheckState( void ); static DG_FILL_MODE_STATE_T handleAcidPumpCheckState( void ); static DG_FILL_MODE_STATE_T handleDialysateProductionState( void ); static DG_FILL_MODE_STATE_T handleDeliverDialysateState( void ); static void handleDialysateMixing( void ); /*********************************************************************//** * @brief * The initFillMode function initializes the fill mode module. * @details Inputs: none * @details Outputs: Fill mode module initialized * @return none *************************************************************************/ void initFillMode( void ) { fillState = DG_FILL_MODE_STATE_START; dialysateFillStartTime = 0; reservoirBaseWeight = 0.0; totalROFlowRate = 0.0; concentrateTestStartTime = 0; initPersistentAlarm( ALARM_ID_ACID_CONDUCTIVITY_OUT_OF_RANGE, CONDUCTIVITY_ERROR_PERSISTENCE_PERIOD_MS, CONDUCTIVITY_ERROR_PERSISTENCE_PERIOD_MS ); initPersistentAlarm( ALARM_ID_DIALYSATE_CONDUCTIVITY_OUT_OF_RANGE, CONDUCTIVITY_ERROR_PERSISTENCE_PERIOD_MS, CONDUCTIVITY_ERROR_PERSISTENCE_PERIOD_MS ); } /*********************************************************************//** * @brief * The transitionToFillMode function prepares for transition to fill mode. * @details Inputs: none * @details Outputs: Re-initialized fill mode * @return none *************************************************************************/ void transitionToFillMode( void ) { initFillMode(); dialysateFillStartTime = getMSTimerCount(); // set initial actuator states setValveState( VDR, VALVE_STATE_DRAIN_C_TO_NO ); setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); turnOnUVReactor( INLET_UV_REACTOR ); turnOnUVReactor( OUTLET_UV_REACTOR ); startPrimaryHeater(); setROPumpTargetFlowRate( TARGET_RO_FLOW_RATE_L, TARGET_RO_PRESSURE_PSI ); } /*********************************************************************//** * @brief * The execFillMode function executes the fill mode state machine. * @details Inputs: fillState * @details Outputs: Check water quality, fill mode state machine executed * @return current state. *************************************************************************/ U32 execFillMode( void ) { // check inlet water conductivity, temperature, pressure, and RO rejection ratio checkInletWaterConductivity(); checkInletWaterTemperature(); checkInletPressure(); checkRORejectionRatio(); // check if run out of time to fill the reservoir if ( didTimeout( dialysateFillStartTime, DIALYSATE_FILL_TIME_OUT ) ) { activateAlarmNoData( ALARM_ID_DG_DIALYSATE_FILL_OUT_OF_TIME ); } // execute current Fill state switch ( fillState ) { case DG_FILL_MODE_STATE_START: fillState = DG_FILL_MODE_STATE_CHECK_INLET_WATER; break; case DG_FILL_MODE_STATE_CHECK_INLET_WATER: fillState = handleCheckInletWaterState(); break; case DG_FILL_MODE_STATE_BICARB_PUMP_CHECK: fillState = handleBicarbPumpCheckState(); break; case DG_FILL_MODE_STATE_ACID_PUMP_CHECK: fillState = handleAcidPumpCheckState(); break; case DG_FILL_MODE_STATE_DIALYSATE_PRODUCTION: fillState = handleDialysateProductionState(); break; case DG_FILL_MODE_STATE_DELIVER_DIALYSATE: fillState = handleDeliverDialysateState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, 0, fillState ) // TODO - add s/w fault enum to 1st data param fillState = DG_FILL_MODE_STATE_START; break; } return fillState; } /*********************************************************************//** * @brief * The isWaterQualityGood function checks for inlet water quality. * @details Inputs: Temperature and conductivity alarms * @details Outputs: none * @return TRUE if water quality is good, otherwise FALSE *************************************************************************/ static BOOL isWaterQualityGood( void ) { BOOL const isInletPressureGood = !isAlarmActive( ALARM_ID_INLET_WATER_LOW_PRESSURE ); BOOL const isWaterTemperatureGood = !isAlarmActive( ALARM_ID_INLET_WATER_HIGH_TEMPERATURE ) && !isAlarmActive( ALARM_ID_INLET_WATER_LOW_TEMPERATURE ); BOOL const isWaterConductivityGood = !isAlarmActive( ALARM_ID_INLET_WATER_HIGH_CONDUCTIVITY ) && !isAlarmActive( ALARM_ID_INLET_WATER_LOW_CONDUCTIVITY ) && !isAlarmActive( ALARM_ID_RO_REJECTION_RATIO_OUT_OF_RANGE ); return ( isInletPressureGood && isWaterTemperatureGood && isWaterConductivityGood ); } /*********************************************************************//** * @brief * The checkDialysateConductivity function checks dialysate conductivity * after adding acid and bicarbonate and triggers an alarm when conductivity * is out of range. * @details Inputs: CD1 and CD2 sensor conductivity * @details Outputs: Trigger alarms when dialysate conductivity is out of allowed range * @return TRUE if dialysate conductivity is good, otherwise FALSE *************************************************************************/ static BOOL checkDialysateConductivity( void ) { BOOL const isDialysateConductivityGood = isDialysateConductivityInRange(); if ( TRUE == isPersistentAlarmTriggered( ALARM_ID_DIALYSATE_CONDUCTIVITY_OUT_OF_RANGE, !isDialysateConductivityGood ) ) { if ( FALSE == isAcidConductivityInRange() ) { SET_ALARM_WITH_1_F32_DATA( ALARM_ID_ACID_CONDUCTIVITY_OUT_OF_RANGE, getConductivityValue( CONDUCTIVITYSENSORS_CD1_SENSOR ) ); } else { SET_ALARM_WITH_1_F32_DATA( ALARM_ID_DIALYSATE_CONDUCTIVITY_OUT_OF_RANGE, getConductivityValue( CONDUCTIVITYSENSORS_CD2_SENSOR ) ); } } if ( TRUE == isPersistentAlarmConditionCleared( ALARM_ID_DIALYSATE_CONDUCTIVITY_OUT_OF_RANGE, !isDialysateConductivityGood ) ) { clearAlarmCondition( ALARM_ID_ACID_CONDUCTIVITY_OUT_OF_RANGE ); clearAlarmCondition( ALARM_ID_DIALYSATE_CONDUCTIVITY_OUT_OF_RANGE ); } return ( isDialysateConductivityGood && isWaterQualityGood() ); } /*********************************************************************//** * @brief * The handleCheckInletWaterState function checks for inlet water quality * before jumping to dialysate production state. * @details Inputs: Temperature and conductivity alarms * @details Outputs: request concentrate pump on and set RO pump flow rate * @return the next state *************************************************************************/ static DG_FILL_MODE_STATE_T handleCheckInletWaterState( void ) { DG_FILL_MODE_STATE_T result = DG_FILL_MODE_STATE_CHECK_INLET_WATER; DG_RESERVOIR_ID_T inactiveReservoir = getInactiveReservoir(); BOOL isInletWaterReady = isWaterQualityGood(); #ifndef DISABLE_DIALYSATE_CHECK if ( isInletWaterReady ) #endif { reservoirBaseWeight = getReservoirWeight( inactiveReservoir ); concentrateTestStartTime = getMSTimerCount(); requestConcentratePumpsOff( CONCENTRATEPUMPS_CP1_ACID ); requestConcentratePumpsOn( CONCENTRATEPUMPS_CP2_BICARB ); result = DG_FILL_MODE_STATE_BICARB_PUMP_CHECK; } return result; } /*********************************************************************//** * @brief * The handleBicarbPumpCheckState function checks conductivity value for * bicarbonate concentrate. * @details Inputs: Bicarbonate conductivity sensor value * @details Outputs: Verified bicarbonate conductivity value * @return the next state *************************************************************************/ static DG_FILL_MODE_STATE_T handleBicarbPumpCheckState( void ) { DG_FILL_MODE_STATE_T result = DG_FILL_MODE_STATE_BICARB_PUMP_CHECK; handleDialysateMixing(); if ( TRUE == isBicarbConductivityInRange() ) { concentrateTestStartTime = getMSTimerCount(); requestConcentratePumpsOn( CONCENTRATEPUMPS_CP1_ACID ); requestConcentratePumpsOff( CONCENTRATEPUMPS_CP2_BICARB ); result = DG_FILL_MODE_STATE_ACID_PUMP_CHECK; } if ( TRUE == didTimeout( concentrateTestStartTime, CONCENTRATE_TEST_TIME_OUT_MS ) ) { SET_ALARM_WITH_1_F32_DATA( ALARM_ID_BICARB_CONDUCTIVITY_OUT_OF_RANGE, getConductivityValue( CONDUCTIVITYSENSORS_CD2_SENSOR ) ); requestNewOperationMode( DG_MODE_CIRC ); } return result; } /*********************************************************************//** * @brief * The handleAcidPumpCheckState function checks conductivity value for * acid concentrate. * @details Inputs: Acid conductivity sensor value * @details Outputs: Verified acid conductivity value * @return the next state *************************************************************************/ static DG_FILL_MODE_STATE_T handleAcidPumpCheckState( void ) { DG_FILL_MODE_STATE_T result = DG_FILL_MODE_STATE_ACID_PUMP_CHECK; handleDialysateMixing(); if ( TRUE == isAcidConductivityInRange() ) { requestConcentratePumpsOn( CONCENTRATEPUMPS_CP1_ACID ); requestConcentratePumpsOn( CONCENTRATEPUMPS_CP2_BICARB ); result = DG_FILL_MODE_STATE_DIALYSATE_PRODUCTION; } if ( TRUE == didTimeout( concentrateTestStartTime, CONCENTRATE_TEST_TIME_OUT_MS ) ) { SET_ALARM_WITH_1_F32_DATA( ALARM_ID_ACID_CONDUCTIVITY_OUT_OF_RANGE, getConductivityValue( CONDUCTIVITYSENSORS_CD1_SENSOR ) ); requestNewOperationMode( DG_MODE_CIRC ); } return result; } /*********************************************************************//** * @brief * The handleDialysateProductionState function executes the dialysate production * state of the fill mode state machine. * @details Inputs: none * @details Outputs: none * @return the next state *************************************************************************/ static DG_FILL_MODE_STATE_T handleDialysateProductionState( void ) { DG_FILL_MODE_STATE_T result = DG_FILL_MODE_STATE_DIALYSATE_PRODUCTION; handleDialysateMixing(); // TODO - transition when temperature and mix is in range #ifndef DISABLE_DIALYSATE_CHECK if ( TRUE == checkDialysateConductivity() ) #endif { setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); result = DG_FILL_MODE_STATE_DELIVER_DIALYSATE; } return result; } /*********************************************************************//** * @brief * The handleDeliverDialysateState function executes the deliver dialysate * state of the fill mode state machine. * @details Inputs: none * @details Outputs: Deliver dialysate * @return the next state *************************************************************************/ static DG_FILL_MODE_STATE_T handleDeliverDialysateState( void ) { DG_FILL_MODE_STATE_T result = DG_FILL_MODE_STATE_DELIVER_DIALYSATE; DG_RESERVOIR_ID_T inactiveReservoir = getInactiveReservoir(); totalROFlowRate += getMeasuredROFlowRate(); handleDialysateMixing(); // TODO - transition back when temperature or mix out of range if ( FALSE == checkDialysateConductivity() ) { #ifndef DISABLE_DIALYSATE_CHECK setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); result = DG_FILL_MODE_STATE_DIALYSATE_PRODUCTION; #endif } // if we've reached our target fill to volume (by weight), we're done filling - go back to re-circ mode if ( hasTargetFillVolumeBeenReached( inactiveReservoir ) ) { F32 const filledWeight = getReservoirWeight( inactiveReservoir ) - reservoirBaseWeight; F32 const integratedVolume = totalROFlowRate * RO_FLOW_INTEGRATOR * ACID_BICARB_CONCENTRATE_ADDITION_MULTIPLER; F32 const integratedVolumeToLoadCellReadingPercent = 1 - ( filledWeight / integratedVolume ); if ( integratedVolumeToLoadCellReadingPercent > FLOW_INTEGRATED_VOLUME_CHECK_TOLERANCE ) { SET_ALARM_WITH_2_F32_DATA( ALARM_ID_DG_FLOW_METER_CHECK_FAILURE, filledWeight, integratedVolume ); } requestNewOperationMode( DG_MODE_CIRC ); } return result; } /*********************************************************************//** * @brief * The handleDialysateMixing function handles the dialysate mixing by setting * the concentrate pump speed relative to the RO pump flow rate. * @details Inputs: none * @details Outputs: Set concentrate pump speed relative to RO pump flow rate * @return none *************************************************************************/ static void handleDialysateMixing( void ) { // Set concentrate pumps speed based off RO pump flow rate F32 const measuredROFlowRate = getMeasuredROFlowRate(); F32 const acidCP1PumpFlowRate = DIALYSATE_ACID_CONCENTRATE_RATIO * measuredROFlowRate * ML_PER_LITER; F32 const bicarbCP2PumpFlowRate = DIALYSATE_BICARB_CONCENTRATE_RATIO * measuredROFlowRate * ML_PER_LITER; setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP1_ACID, acidCP1PumpFlowRate ); setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP2_BICARB, bicarbCP2PumpFlowRate ); } /**@}*/