Index: firmware/App/Modes/ModeFill.c =================================================================== diff -u -re14edafc9a5a0105eb207c5f574881984f89d65f -r8defcfa32034c4068b733930c2e8b0989c55daba --- firmware/App/Modes/ModeFill.c (.../ModeFill.c) (revision e14edafc9a5a0105eb207c5f574881984f89d65f) +++ firmware/App/Modes/ModeFill.c (.../ModeFill.c) (revision 8defcfa32034c4068b733930c2e8b0989c55daba) @@ -1,165 +1,765 @@ /************************************************************************** - * - * Copyright (c) 2019-2019 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 - * - * @date 19-Nov-2019 - * @author L. Baloa - * - * @brief Top-level state machine for the fill mode. - * - **************************************************************************/ +* +* Copyright (c) 2019-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 ModeFill.c +* +* @author (last) Dara Navaei +* @date (last) 05-Jan-2022 +* +* @author (original) Leonardo Baloa +* @date (original) 19-Nov-2019 +* +***************************************************************************/ +#include // For fabs + +#include "ConcentratePumps.h" +#include "ConductivitySensors.h" +#include "FPGA.h" +#include "Heaters.h" +#include "LoadCell.h" #include "ModeFill.h" -#include "Common.h" +#include "NVDataMgmtDGRecords.h" +#include "NVDataMgmt.h" #include "OperationModes.h" -#include +#include "PersistentAlarm.h" +#include "Pressures.h" +#include "Reservoirs.h" +#include "ROPump.h" +#include "SystemComm.h" +#include "TaskGeneral.h" +#include "TemperatureSensors.h" +#include "Timers.h" +#include "UVReactors.h" +#include "Valves.h" -#ifdef RM46_EVAL_BOARD_TARGET - #include "CPLD.h" -#endif +/** + * @addtogroup DGFillMode + * @{ + */ +// ********** private definitions ********** + +#define TARGET_RO_PRESSURE_PSI 130 ///< Target pressure for RO pump. + +#define DIALYSATE_FILL_TIME_OUT ( 5 * SEC_PER_MIN * MS_PER_SECOND ) ///< Time out period when reservoir is not filled with correct dialysate. +#define EMPTY_BOTTLE_DETECT_PERSISTENT_PERIOD_MS ( 5 * MS_PER_SECOND ) ///< Persistent period for empty bottle detect. + +#define CONCENTRATE_PUMP_PRIME_INTERVAL ( 3 * MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Persistent time interval for concentrate pumps prime. + +#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_PUMP_PRIME_EXTRA_SPEED_ML_MIN 5.0 ///< Concentrate pump additional speed during priming in mL/min. +#define CONCENTRATE_TEST_TIME_OUT_MS ( 45 * MS_PER_SECOND ) ///< Concentrate test time out period in ms. +#define WATER_QUALITY_CHECK_TIME_OUT_MS ( 30 * MS_PER_SECOND ) ///< Inlet water quality check time out period in ms. + +#define DIALYSATE_TEMPERATURE_TOLERANCE_C 2.0 ///< Dialysate temperature tolerance in degree C. +#define DIALYSATE_TEMPERATURE_SENSORS_MAX_DRIFT_C 2.0 ///< Dialysate temperature sensors maximum allowed drift in C. +#define DIALYSATE_TEMPERATURE_SENSORS_DRIFT_TIMEOUT_MS ( 10 * MS_PER_SECOND ) ///< Dialysate temperature sensors drift timeout in milliseconds. + +#define ACID_CONCENTRATION_BOTTLE_VOLUME_ML 3000.0 ///< Volume of acid concentration in ml. +#define BICARB_CONCENTRATION_BOTTLE_VOLUME_ML 3000.0 ///< Volume of bicarb concentration in ml. +#define CONCENTRATION_BOTTLE_LOW_VOLUME_ML 100.0 ///< Concentration bottle low volume in ml. + +/// Multiplier to convert flow (mL/min) into volume (mL) for period of general task interval. +static const F32 FLOW_INTEGRATOR = ( (F32)TASK_GENERAL_INTERVAL / (F32)( SEC_PER_MIN * MS_PER_SECOND ) ); + // ********** private data ********** -static volatile enum FillModeSubModes + +/// Fill conditions status +typedef struct { - CHECK_INLET_WATER = 0, - CREATE_PRODUCT_WATER, - DIALYSATE_PRODUCTION, - DELIVER_DIALYSATE -} fillCurrentSubMode; + F32 fillFlowRateRunningSum; ///< Fill flow running sum. + U32 fillSampleCounter; ///< Fill flow sample counter. + F32 fillTemperatureRunningSum; ///< Fill temperature running sum. + F32 fillTemperatureAverage; ///< Fill temperature average value. + F32 fillFlowRateAverage; ///< Fill flow average value. + F32 fillLastTemperature; ///< Fill last temperature value. + BOOL isThisFirstFill; ///< Fill flag to indicate whether it is the first fill or not. +} FILL_CONDITION_STATUS_T; -static U32 timer; -static U16 toggle_counter = 0; +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 FILL_CONDITION_STATUS_T fillStatus; ///< Fill condition status. -#define QUARTER_SECOND 250 -#define HALF_SECOND 500 +static U32 waterQualityCheckStartTime; ///< Starting time for inlet water quality check. +static U32 concentrateTestStartTime; ///< Starting time for concentrate test. +static U32 concentratePumpPrimeCount; ///< Interval count for concentrate pump prime. +static F32 totalROFlowRateMLPM; ///< Total RO flow rate over period of time. +static F32 acidConductivityTotal; ///< Total of acid conductivity during fill. +static F32 dialysateConductivityTotal; ///< Total of dialysate conductivity during fill. +static U32 conductivitySampleCount; ///< Sample count of conductivity during fill. +static OVERRIDE_F32_T usedAcidVolumeML = { 0.0, 0.0, 0.0, 0.0 }; ///< The integrated acid concentration volume has been used in ml. +static OVERRIDE_F32_T usedBicarbVolumeML = { 0.0, 0.0, 0.0, 0.0 }; ///< The integrated bicarb concentration volume has been used in ml. + // ********** private function prototypes ********** -/************************************************************************* - * @brief initFillMode - * The initFillMode function initializes the Fill Mode module. - * @details - * Inputs : none - * Outputs : Fill Mode module initialized. - * @param none +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 DG_FILL_MODE_STATE_T handlePausedState( void ); + +static BOOL isWaterQualityGood( void ); +static void checkDialysateTemperatureSensorsDrift( void ); +static BOOL checkDialysateTemperature( void ); +static void handleDialysateMixing( F32 measuredROFlowRate_mL_min ); +static void setFillInfoToRTCRAM( void ); + +/*********************************************************************//** + * @brief + * The initFillMode function initializes the fill mode module. + * @details Inputs: none + * @details Outputs: fillState, dialysateFillStartTime, reservoirBaseWeight, + * totalROFlowRateMLPM, concentrateTestStartTime, acidConductivityTotal, + * dialysateConductivityTotal, conductivitySampleCount, + * concentratePumpPrimeCount * @return none *************************************************************************/ void initFillMode( void ) { - fillCurrentSubMode = CHECK_INLET_WATER; - timer = getMSTimerCount(); + fillState = DG_FILL_MODE_STATE_START; + dialysateFillStartTime = 0; + reservoirBaseWeight = 0.0; + totalROFlowRateMLPM = 0.0; + concentrateTestStartTime = 0; + acidConductivityTotal = 0.0; + dialysateConductivityTotal = 0.0; + conductivitySampleCount = 0; + concentratePumpPrimeCount = 0; + + initPersistentAlarm( ALARM_ID_ACID_CONDUCTIVITY_OUT_OF_RANGE, 0, EMPTY_BOTTLE_DETECT_PERSISTENT_PERIOD_MS ); + initPersistentAlarm( ALARM_ID_BICARB_CONDUCTIVITY_OUT_OF_RANGE, 0, EMPTY_BOTTLE_DETECT_PERSISTENT_PERIOD_MS ); + initPersistentAlarm( ALARM_ID_DG_DIALYSATE_TEMPERATURE_SENSORS_DRFIT_TIMEOUT, 0, DIALYSATE_TEMPERATURE_SENSORS_DRIFT_TIMEOUT_MS ); } -/************************************************************************* - * @brief transitionToFillMode - * The transitionToFillMode function prepares for transition to \n - * fill mode. - * @details - * Inputs : none - * Outputs : - * @param none +/*********************************************************************//** + * @brief + * The transitionToFillMode function prepares for transition to fill mode. + * @details Inputs: none + * @details Outputs: Re-initialized fill mode * @return none *************************************************************************/ -void transitionToFillMode( void ) +U32 transitionToFillMode( void ) { - fillCurrentSubMode = CHECK_INLET_WATER; - timer = getMSTimerCount(); - toggle_counter = 0; + DG_RESERVOIR_ID_T inactiveReservoir = getInactiveReservoir(); + + initFillMode(); + reservoirBaseWeight = getReservoirWeight( inactiveReservoir ); + 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 ); + + // 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 +#ifndef DISABLE_FLOW_CONTROL_TREATMENT + setROPumpTargetFlowRateLPM( getTargetFillFlowRateLPM(), TARGET_RO_PRESSURE_PSI ); +#endif + setHeaterTargetTemperature( DG_PRIMARY_HEATER, getPrimaryHeaterTargetTemperature() ); + startHeater( DG_PRIMARY_HEATER ); + + return fillState; } -/************************************************************************* - * @brief execFillMode - * The execFillMode function executes the Fill Mode state machine. - * @details - * Inputs : none - * Outputs : - * @param none - * @return none +/*********************************************************************//** + * @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. *************************************************************************/ -void execFillMode( void ) +U32 execFillMode( void ) { + // Check inlet water conductivity, temperature, pressure, and RO rejection ratio + checkInletWaterConductivity(); + checkInletWaterTemperature(); + checkInletPressure(); + checkRORejectionRatio(); + checkDialysateTemperatureSensorsDrift(); - switch (fillCurrentSubMode){ + // TODO: Check for open straw door status and alarm if closed + // Check if run out of time to fill the reservoir + if ( TRUE == didTimeout( dialysateFillStartTime, DIALYSATE_FILL_TIME_OUT ) ) + { + activateAlarmNoData( ALARM_ID_DG_DIALYSATE_FILL_OUT_OF_TIME ); + requestNewOperationMode( DG_MODE_GENE ); + } - case CHECK_INLET_WATER: + // Execute current Fill state + switch ( fillState ) + { + case DG_FILL_MODE_STATE_START: + waterQualityCheckStartTime = getMSTimerCount(); + fillState = DG_FILL_MODE_STATE_CHECK_INLET_WATER; + break; - // We check every half second to toggle LED - if (TRUE == didTimeout(timer, QUARTER_SECOND)) - { - timer = getMSTimerCount(); // Reset timer - #ifdef RM46_EVAL_BOARD_TARGET - toggleUserLED(); - #endif - toggle_counter++; - } + case DG_FILL_MODE_STATE_CHECK_INLET_WATER: + fillState = handleCheckInletWaterState(); + break; - if (toggle_counter == 8) - { - toggle_counter = 0; + case DG_FILL_MODE_STATE_BICARB_PUMP_CHECK: + fillState = handleBicarbPumpCheckState(); + break; - // switch to submode - fillCurrentSubMode = CREATE_PRODUCT_WATER; - } + case DG_FILL_MODE_STATE_ACID_PUMP_CHECK: + fillState = handleAcidPumpCheckState(); + break; + case DG_FILL_MODE_STATE_DIALYSATE_PRODUCTION: + fillState = handleDialysateProductionState(); break; - case CREATE_PRODUCT_WATER: + case DG_FILL_MODE_STATE_DELIVER_DIALYSATE: + fillState = handleDeliverDialysateState(); + break; - // We check every half second to toggle LED - if (TRUE == didTimeout(timer, HALF_SECOND)) - { - timer = getMSTimerCount(); // Reset timer - #ifdef RM46_EVAL_BOARD_TARGET - toggleUserLED(); - #endif - toggle_counter++; - } + case DG_FILL_MODE_STATE_PAUSED: + fillState = handlePausedState(); + break; - if (toggle_counter == 4) - { - toggle_counter = 0; - - // switch to submode - fillCurrentSubMode = DIALYSATE_PRODUCTION; - } + default: + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_FILL_MODE_INVALID_EXEC_STATE, fillState ) + fillState = DG_FILL_MODE_STATE_START; break; + } - case DIALYSATE_PRODUCTION: - // We check every half second to toggle LED - if (TRUE == didTimeout(timer, HALF_SECOND + QUARTER_SECOND)) - { - timer = getMSTimerCount(); // Reset timer - #ifdef RM46_EVAL_BOARD_TARGET - toggleUserLED(); - #endif - toggle_counter++; - } + return fillState; +} - if (toggle_counter == 4) - { - toggle_counter = 0; +/*********************************************************************//** + * @brief + * The getAvgFillFlowRate function returns the average fill flow rate in + * each fill. + * @details Inputs: none + * @details Outputs: fillFlowRateAverage + * @return average of the fill flow rate + *************************************************************************/ +F32 getAvgFillFlowRate( void ) +{ + return fillStatus.fillFlowRateAverage; +} - // switch to submode - fillCurrentSubMode = DELIVER_DIALYSATE; - } - break; +/*********************************************************************//** + * @brief + * The getAverageFillTemperature function returns the average fill temperature + * in each fill. + * @details Inputs: none + * @details Outputs: fillTemperatureAverage + * @return average fill temperature + *************************************************************************/ +F32 getAvgFillTemperature( void ) +{ + return fillStatus.fillTemperatureAverage; +} - case DELIVER_DIALYSATE: +/*********************************************************************//** + * @brief + * The getLastFillTemperature function returns the last fill temperature + * in each fill. + * @details Inputs: none + * @details Outputs: fillLastTemperature + * @return last fill temperature + *************************************************************************/ +F32 getLastFillTemperature( void ) +{ + return fillStatus.fillLastTemperature; +} - if(toggle_counter == 0) - { - #ifdef RM46_EVAL_BOARD_TARGET - setUserLED(TRUE); - #endif - toggle_counter++; - } - break; +/*********************************************************************//** + * @brief + * The resetFillStatusParameters function resets the fill status parameters. + * @details Inputs: none + * @details Outputs: fillStatus + * @return none + *************************************************************************/ +void resetFillStatusParameters( void ) +{ + DG_HEATERS_RECORD_T heaterInfo; + // There is no number of NV data to check for the heaters info so it is passed as 0 + U08 numOfNVData2Check = 0; + // Get the heaters info from the NV data management. Do not alarm on the status of the data since it has already been checked in NV POST + getNVRecord2Driver( GET_INF_HEATERS_RECORD, (U08*)&heaterInfo, sizeof( DG_HEATERS_RECORD_T ), numOfNVData2Check, ALARM_ID_NO_ALARM ); + + // If the data in the NV data management was not initialized properly, set it to 0 otherwise, set the average flow rate + fillStatus.fillFlowRateAverage = ( heaterInfo.averageFillFlow < NEARLY_ZERO ? 0.0 : heaterInfo.averageFillFlow ); + fillStatus.fillFlowRateRunningSum = 0.0; + fillStatus.fillSampleCounter = 0; + fillStatus.fillTemperatureRunningSum = 0.0; + // At the beginning the last and average temperatures are considered as the trimmer heater target temperature which + // is the dialysate temperature + fillStatus.fillTemperatureAverage = getHeaterTargetTemperature( DG_TRIMMER_HEATER ); + fillStatus.fillLastTemperature = getHeaterTargetTemperature( DG_TRIMMER_HEATER ) + RESERVOIR_EXTRA_TEMPERATURE; + fillStatus.isThisFirstFill = TRUE; +} + +/*********************************************************************//** + * @brief + * The isThisTheFirstFill function returns the boolean flag that indicates + * whether this is the first fill of a treatment or not. + * @details Inputs: none + * @details Outputs: none + * @return fillStatus.isThisFirstFill + *************************************************************************/ +BOOL isThisTheFirstFill( void ) +{ + return fillStatus.isThisFirstFill; +} + +/*********************************************************************//** + * @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; + +#ifndef DISABLE_DIALYSATE_CHECK + if ( TRUE == isWaterQualityGood() ) +#endif + { + // If this is the first fill of a treatment, prime and acid and bicarb lines, otherwise transition + // to dialysate production directly + if ( TRUE == isThisTheFirstFill() ) + { + concentrateTestStartTime = getMSTimerCount(); + #ifndef DISABLE_MIXING + + setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP2_BICARB, CONCENTRATE_PUMP_MAX_SPEED ); + requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID ); + requestConcentratePumpOn( CONCENTRATEPUMPS_CP2_BICARB ); + #endif + result = DG_FILL_MODE_STATE_BICARB_PUMP_CHECK; + } + else + { + result = DG_FILL_MODE_STATE_DIALYSATE_PRODUCTION; + } } + + if ( TRUE == didTimeout( waterQualityCheckStartTime, WATER_QUALITY_CHECK_TIME_OUT_MS ) ) + { + activateAlarmNoData( ALARM_ID_DG_BAD_INLET_WATER_QUALITY ); + requestNewOperationMode( DG_MODE_GENE ); + } + + 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; + + DG_BICARB_CONCENTRATES_RECORD_T bicarb = getBicarbConcentrateCalRecord(); + F32 measuredROFlowRateMLPM = getMeasuredROFlowRateLPM() * ML_PER_LITER; + F32 bicarbPumpFlowRateMLPM = measuredROFlowRateMLPM * bicarb.bicarbConcentrate[ CAL_DATA_BICARB_CONCENTRATE_1 ].bicarbConcMixRatio + + CONCENTRATE_PUMP_PRIME_EXTRA_SPEED_ML_MIN; + +#ifndef DISABLE_DIALYSATE_CHECK + F32 bicarbConductivity = getConductivityValue( CONDUCTIVITYSENSORS_CD2_SENSOR ); +#else + F32 bicarbConductivity = MAX_BICARB_CONCENTRATE_CONDUCTIVITY; +#endif + + bicarbPumpFlowRateMLPM = MIN( bicarbPumpFlowRateMLPM, CONCENTRATE_PUMP_MAX_SPEED ); + + if ( bicarbConductivity >= MIN_BICARB_CONCENTRATE_CONDUCTIVITY ) + { + // Reduce acid pump speed after reaching minimum conductivity + // This prevents conductivity value to go out of sensor's range + setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP2_BICARB, bicarbPumpFlowRateMLPM ); + + if ( concentratePumpPrimeCount++ > CONCENTRATE_PUMP_PRIME_INTERVAL ) + { + concentratePumpPrimeCount = 0; + concentrateTestStartTime = getMSTimerCount(); +#ifndef DISABLE_MIXING + setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP1_ACID, CONCENTRATE_PUMP_MAX_SPEED ); + requestConcentratePumpOn( CONCENTRATEPUMPS_CP1_ACID ); + requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB ); +#endif + + result = DG_FILL_MODE_STATE_ACID_PUMP_CHECK; + } + } + else + { + concentratePumpPrimeCount = 0; + } + + if ( TRUE == didTimeout( concentrateTestStartTime, CONCENTRATE_TEST_TIME_OUT_MS ) ) + { + SET_ALARM_WITH_1_F32_DATA( ALARM_ID_BICARB_CONDUCTIVITY_OUT_OF_RANGE, bicarbConductivity ); + requestNewOperationMode( DG_MODE_GENE ); + } + + 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; + + DG_ACID_CONCENTRATES_RECORD_T acid = getAcidConcentrateCalRecord(); + F32 measuredROFlowRateMLPM = getMeasuredROFlowRateLPM() * ML_PER_LITER; + F32 acidPumpFlowRateMLPM = measuredROFlowRateMLPM * acid.acidConcentrate[ CAL_DATA_ACID_CONCENTRATE_1 ].acidConcMixRatio + + CONCENTRATE_PUMP_PRIME_EXTRA_SPEED_ML_MIN; + +#ifndef DISABLE_DIALYSATE_CHECK + F32 acidConductivity = getConductivityValue( CONDUCTIVITYSENSORS_CD1_SENSOR ); +#else + F32 acidConductivity = MAX_ACID_CONCENTRATE_CONDUCTIVITY; +#endif + + acidPumpFlowRateMLPM = MIN( acidPumpFlowRateMLPM, CONCENTRATE_PUMP_MAX_SPEED ); + + if ( acidConductivity >= MIN_ACID_CONCENTRATE_CONDUCTIVITY ) + { + // Reduce acid pump speed after reaching minimum conductivity + // This prevents conductivity value to go out of sensor's range + setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP1_ACID, acidPumpFlowRateMLPM ); + + if ( concentratePumpPrimeCount++ > CONCENTRATE_PUMP_PRIME_INTERVAL ) + { + concentratePumpPrimeCount = 0; + fillStatus.isThisFirstFill = FALSE; + requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID ); + requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB ); + result = DG_FILL_MODE_STATE_DIALYSATE_PRODUCTION; + } + } + else + { + concentratePumpPrimeCount = 0; + } + + if ( TRUE == didTimeout( concentrateTestStartTime, CONCENTRATE_TEST_TIME_OUT_MS ) ) + { + SET_ALARM_WITH_1_F32_DATA( ALARM_ID_ACID_CONDUCTIVITY_OUT_OF_RANGE, acidConductivity ); + requestNewOperationMode( DG_MODE_GENE ); + } + + return result; +} + +/*********************************************************************//** + * @brief + * The handleDialysateProductionState function executes the dialysate production + * state of the fill mode state machine. + * @details Inputs: inlet water quality and dialysate temperature + * @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; + F32 measuredROFlowRateMLPM = getMeasuredROFlowRateLPM() * ML_PER_LITER; + +#ifndef DISABLE_DIALYSATE_CHECK + if ( ( TRUE == isWaterQualityGood() ) && ( TRUE == checkDialysateTemperature() ) ) +#else + if ( TRUE ) +#endif + { + // Prime mixing before deliver result to reservoir + handleDialysateMixing( measuredROFlowRateMLPM ); +#ifndef DISABLE_MIXING + requestConcentratePumpOn( CONCENTRATEPUMPS_CP1_ACID ); + requestConcentratePumpOn( CONCENTRATEPUMPS_CP2_BICARB ); +#endif + setValveState( VPO, VALVE_STATE_FILL_C_TO_NC ); + result = DG_FILL_MODE_STATE_DELIVER_DIALYSATE; + } + else + { + concentratePumpPrimeCount = 0; + requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID ); + requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB ); + } + + return result; +} + +/*********************************************************************//** + * @brief + * The handleDeliverDialysateState function executes the deliver dialysate + * state of the fill mode state machine. + * @details Inputs: inlet water quality and dialysate temperature + * @details Outputs: Deliver dialysate to inactive reservoir + * @return the next state + *************************************************************************/ +static DG_FILL_MODE_STATE_T handleDeliverDialysateState( void ) +{ + F32 integratedVolumeML; + + F32 measuredROFlowRateMLPM = getMeasuredROFlowRateLPM() * ML_PER_LITER; + F32 acidConductivity = getConductivityValue( CONDUCTIVITYSENSORS_CD1_SENSOR ); + F32 dialysateConductivity = getConductivityValue( CONDUCTIVITYSENSORS_CD2_SENSOR ); + BOOL isAcidConductivityOutOfRange = ( acidConductivity <= MIN_ACID_CONCENTRATE_CONDUCTIVITY ) || + ( acidConductivity >= MAX_ACID_CONCENTRATE_CONDUCTIVITY ) ? TRUE : FALSE; + BOOL isDialysateConductivityOutOfRange = ( dialysateConductivity <= MIN_DIALYSATE_CONDUCTIVITY ) || + ( dialysateConductivity >= MAX_DIALYSATE_CONDUCTIVITY ) ? TRUE : FALSE; + + DG_FILL_MODE_STATE_T result = DG_FILL_MODE_STATE_DELIVER_DIALYSATE; + DG_RESERVOIR_ID_T inactiveReservoir = getInactiveReservoir(); + + // Set concentrate pumps speed based off RO pump flow rate + handleDialysateMixing( measuredROFlowRateMLPM ); + + totalROFlowRateMLPM += measuredROFlowRateMLPM; + integratedVolumeML = totalROFlowRateMLPM * FLOW_INTEGRATOR * ACID_BICARB_CONCENTRATE_ADDITION_MULTIPLER; + usedAcidVolumeML.data += getMeasuredPumpSpeed( CONCENTRATEPUMPS_CP1_ACID ) * FLOW_INTEGRATOR; + usedBicarbVolumeML.data += getMeasuredPumpSpeed( CONCENTRATEPUMPS_CP2_BICARB ) * FLOW_INTEGRATOR; + + acidConductivityTotal += acidConductivity; + dialysateConductivityTotal += dialysateConductivity; + conductivitySampleCount++; + + // DG is delivering dialysate keep collecting the sample counter and the measured flow + fillStatus.fillSampleCounter += 1; + fillStatus.fillFlowRateRunningSum += getMeasuredROFlowRateLPM(); + fillStatus.fillTemperatureRunningSum += getTemperatureValue( (U32)TEMPSENSORS_OUTLET_PRIMARY_HEATER ); + +#ifndef DISABLE_DIALYSATE_CHECK + if ( ( isWaterQualityGood() != TRUE ) || ( checkDialysateTemperature() != TRUE ) ) + { + requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID ); + requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB ); + setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); + result = DG_FILL_MODE_STATE_DIALYSATE_PRODUCTION; + } + + if ( TRUE == isPersistentAlarmTriggered( ALARM_ID_ACID_CONDUCTIVITY_OUT_OF_RANGE, isAcidConductivityOutOfRange ) ) + { + usedAcidVolumeML.data = 0.0; + requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID ); + requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB ); + setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); + result = DG_FILL_MODE_STATE_PAUSED; + } + + if ( TRUE == isPersistentAlarmTriggered( ALARM_ID_BICARB_CONDUCTIVITY_OUT_OF_RANGE, isDialysateConductivityOutOfRange ) ) + { + usedBicarbVolumeML.data = 0.0; + requestConcentratePumpOff( CONCENTRATEPUMPS_CP1_ACID ); + requestConcentratePumpOff( CONCENTRATEPUMPS_CP2_BICARB ); + setValveState( VPO, VALVE_STATE_NOFILL_C_TO_NO ); + result = DG_FILL_MODE_STATE_PAUSED; + } +#endif +#ifndef DISABLE_MIXING + if ( ( ACID_CONCENTRATION_BOTTLE_VOLUME_ML - getF32OverrideValue( &usedAcidVolumeML ) ) <= CONCENTRATION_BOTTLE_LOW_VOLUME_ML ) + { + activateAlarmNoData( ALARM_ID_DG_ACID_BOTTLE_LOW_VOLUME ); + } + + if ( ( BICARB_CONCENTRATION_BOTTLE_VOLUME_ML - getF32OverrideValue( &usedBicarbVolumeML ) ) <= CONCENTRATION_BOTTLE_LOW_VOLUME_ML ) + { + activateAlarmNoData( ALARM_ID_DG_BICARB_BOTTLE_LOW_VOLUME ); + } +#endif + // If we've reached our target fill to volume (by weight), we're done filling - go back to generation idle mode + if ( TRUE == hasTargetFillVolumeBeenReached( inactiveReservoir ) ) + { + F32 filledVolumeML = getReservoirWeight( inactiveReservoir ) - reservoirBaseWeight; + F32 integratedVolumeToLoadCellReadingPercent = fabs( 1 - ( filledVolumeML / integratedVolumeML ) ); + F32 avgAcidConductivity = acidConductivityTotal / conductivitySampleCount; // TODO - should we be checking this below? + F32 avgDialysateConductivity = dialysateConductivityTotal / conductivitySampleCount; + + if ( integratedVolumeToLoadCellReadingPercent > FLOW_INTEGRATED_VOLUME_CHECK_TOLERANCE ) + { +#ifndef DISABLE_FLOW_CHECK_IN_FILL + SET_ALARM_WITH_2_F32_DATA( ALARM_ID_DG_FLOW_METER_CHECK_FAILURE, filledVolumeML, integratedVolumeML ); +#endif + } + +#ifndef DISABLE_DIALYSATE_CHECK + if ( ( avgDialysateConductivity < MIN_DIALYSATE_CONDUCTIVITY ) || ( avgDialysateConductivity > MAX_DIALYSATE_CONDUCTIVITY ) ) + { + SET_ALARM_WITH_2_F32_DATA( ALARM_ID_DIALYSATE_CONDUCTIVITY_FAULT, avgAcidConductivity, avgDialysateConductivity ); + } +#endif + + // Done with this fill. Calculate the average fill flow rate and average temperature + // Reset the variables for the next fill + // Get the last fill temperature before leaving to Generation Idle + fillStatus.fillFlowRateAverage = fillStatus.fillFlowRateRunningSum / (F32)fillStatus.fillSampleCounter; + fillStatus.fillTemperatureAverage = fillStatus.fillTemperatureRunningSum / (F32)fillStatus.fillSampleCounter; + fillStatus.fillFlowRateRunningSum = 0.0; + fillStatus.fillTemperatureRunningSum = 0.0; + fillStatus.fillSampleCounter = 1; + fillStatus.fillLastTemperature = getTemperatureValue( (U32)TEMPSENSORS_OUTLET_PRIMARY_HEATER ); + + // Write the latest fill data into the RTC RAM for heaters control + // TODO test this and make sure it is writing it correctly + setFillInfoToRTCRAM(); + + requestNewOperationMode( DG_MODE_GENE ); + } + + return result; +} + +/*********************************************************************//** + * @brief + * The handlePausedState function executes the paused state of the fill + * mode state machine. + * @details Inputs: Empty bottle alarm active + * @details Outputs: none + * @return the next state + *************************************************************************/ +static DG_FILL_MODE_STATE_T handlePausedState( void ) +{ + DG_FILL_MODE_STATE_T result = DG_FILL_MODE_STATE_PAUSED; + + if ( ( FALSE == isAlarmActive( ALARM_ID_ACID_CONDUCTIVITY_OUT_OF_RANGE ) ) && + ( FALSE == isAlarmActive( ALARM_ID_BICARB_CONDUCTIVITY_OUT_OF_RANGE ) ) ) + { + result = DG_FILL_MODE_STATE_CHECK_INLET_WATER; + } + + return result; +} + +/*********************************************************************//** + * @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 isInletPressureGood = ( FALSE == isAlarmActive( ALARM_ID_INLET_WATER_LOW_PRESSURE ) ? FALSE : TRUE ); + + BOOL isWaterTemperatureGood = ( ( FALSE == isAlarmActive( ALARM_ID_INLET_WATER_HIGH_TEMPERATURE ) && + FALSE == isAlarmActive( ALARM_ID_INLET_WATER_LOW_TEMPERATURE ) ) ? FALSE : TRUE ); + + BOOL isWaterConductivityGood = ( ( FALSE == isAlarmActive( ALARM_ID_INLET_WATER_HIGH_CONDUCTIVITY ) && + FALSE == isAlarmActive( ALARM_ID_INLET_WATER_LOW_CONDUCTIVITY ) && + FALSE == isAlarmActive( ALARM_ID_RO_REJECTION_RATIO_OUT_OF_RANGE ) ) ? FALSE : TRUE ); + + return ( ( isInletPressureGood && isWaterTemperatureGood && isWaterConductivityGood ) ); +} + +/*********************************************************************//** + * @brief + * The checkDialysateTemperatureSensorsDrift function checks whether the + * dialysate temperature sensors have drifted. If they are drifted, it raises + * an alarm. + * @details Inputs: none + * @details Outputs: None + * @return none + *************************************************************************/ +static void checkDialysateTemperatureSensorsDrift( void ) +{ +#ifndef THD_USING_TRO_CONNECTOR // Do not use until TRo is back in line + F32 TDi = getTemperatureValue( TEMPSENSORS_INLET_DIALYSATE ); + F32 TRo = getTemperatureValue( TEMPSENSORS_OUTLET_REDUNDANT ); + BOOL isDriftOut = ( fabs( TDi - TRo ) >= DIALYSATE_TEMPERATURE_SENSORS_MAX_DRIFT_C ? TRUE : FALSE ); + + if ( TRUE == isPersistentAlarmTriggered( ALARM_ID_DG_DIALYSATE_TEMPERATURE_SENSORS_DRFIT_TIMEOUT, isDriftOut ) ) + { + activateAlarmNoData( ALARM_ID_DG_DIALYSATE_TEMPERATURE_SENSORS_DRFIT_TIMEOUT ); + } +#endif +} + +/*********************************************************************//** + * @brief + * The checkDialysateTemperature function checks dialysate temperature after + * it gets heated up by primary heater. + * @details Inputs: TPo temperature value + * @details Outputs: None + * @return TRUE if dialysate temperature is in range, otherwise FALSE + *************************************************************************/ +static BOOL checkDialysateTemperature( void ) +{ + F32 dialysateTemp = getTemperatureValue( TEMPSENSORS_OUTLET_PRIMARY_HEATER ); + F32 targetTemp = getHeaterTargetTemperature( DG_PRIMARY_HEATER ); + + return ( ( fabs( dialysateTemp - targetTemp ) <= DIALYSATE_TEMPERATURE_TOLERANCE_C ) ? TRUE : FALSE ); +} + +/*********************************************************************//** + * @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 + * @param measuredROFlowRate_mL_min measured RO flow rate in mL/min + * @return none + *************************************************************************/ +static void handleDialysateMixing( F32 measuredROFlowRate_mL_min ) +{ +#ifndef DISABLE_MIXING + // TODO what should we do with start volume from the structure? + DG_ACID_CONCENTRATES_RECORD_T acid = getAcidConcentrateCalRecord(); + DG_BICARB_CONCENTRATES_RECORD_T bicarb = getBicarbConcentrateCalRecord(); + + // Set concentrate pumps speed based off RO pump flow rate + F32 acidCP1PumpFlowRate = acid.acidConcentrate[ CAL_DATA_ACID_CONCENTRATE_1 ].acidConcMixRatio * measuredROFlowRate_mL_min; + F32 bicarbCP2PumpFlowRate = bicarb.bicarbConcentrate[ CAL_DATA_BICARB_CONCENTRATE_1 ].bicarbConcMixRatio * measuredROFlowRate_mL_min; + + // Cap to the maximum allowed concentrate pumps rate + acidCP1PumpFlowRate = MIN( acidCP1PumpFlowRate, CONCENTRATE_PUMP_MAX_SPEED ); + bicarbCP2PumpFlowRate = MIN( bicarbCP2PumpFlowRate, CONCENTRATE_PUMP_MAX_SPEED ); + + setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP1_ACID, acidCP1PumpFlowRate ); + setConcentratePumpTargetSpeed( CONCENTRATEPUMPS_CP2_BICARB, bicarbCP2PumpFlowRate ); +#endif +} + +/*********************************************************************//** + * @brief + * The setFillInfoToRTCRAM function writes the fill information to the RTC + * RAM at the end of each fill. This information is used for dialysate temperature + * control. + * @details Inputs: fillStatus.fillFlowRateAverage + * @details Outputs: none + * @return none + *************************************************************************/ +static void setFillInfoToRTCRAM( void ) +{ + DG_HEATERS_RECORD_T record; + + record.averageFillFlow = fillStatus.fillFlowRateAverage; + + setHeatersInfoRecord( (U08*)&record, sizeof( DG_HEATERS_RECORD_T ) ); +} + +/**@}*/