Index: firmware/App/Services/Reservoirs.c =================================================================== diff -u -r9779d54c908734795f7b03b0ba8c409fc3115437 -r965eb10d9407c25e8cf334623ad45e126cecee97 --- firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision 9779d54c908734795f7b03b0ba8c409fc3115437) +++ firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision 965eb10d9407c25e8cf334623ad45e126cecee97) @@ -1,25 +1,31 @@ /************************************************************************** * -* 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 Reservoirs.c +* @file Reservoirs.c * -* @author (last) Quang Nguyen -* @date (last) 26-Aug-2020 +* @author (last) Dara Navaei +* @date (last) 23-May-2022 * -* @author (original) Sean -* @date (original) 18-Mar-2020 +* @author (original) Sean +* @date (original) 18-Mar-2020 * ***************************************************************************/ #include // for memcpy() +#include "DrainPump.h" +#include "Heaters.h" #include "LoadCell.h" -#include "ModeRecirculate.h" +#include "MessageSupport.h" +#include "ModeDrain.h" +#include "ModeFill.h" +#include "ModeGenIdle.h" #include "OperationModes.h" +#include "Pressures.h" #include "Reservoirs.h" #include "SystemCommMessages.h" #include "TaskGeneral.h" @@ -33,75 +39,157 @@ // ********** private definitions ********** -#define MIN_RESERVOIR_VOLUME_ML 0 ///< Minimum reservoir volume in mL. -#define DEFAULT_FILL_VOLUME_ML 1700 ///< Default fill volume for treatment in mL. -#define DISINFECT_FILL_VOLUME_ML 2400 ///< Fill volume for disinfection in mL. -#define MAX_FILL_VOLUME_ML MAX_RESERVOIR_VOLUME_ML ///< Maximum fill volume in mL. -#define DEFAULT_DRAIN_VOLUME_ML 0 ///< Default drain volume in mL. -#define MAX_DRAIN_VOLUME_ML MAX_RESERVOIR_VOLUME_ML ///< Maximum drain volume in mL. +#define MIN_RESERVOIR_VOLUME_ML 0 ///< Minimum reservoir volume in mL. +#define DEFAULT_FILL_VOLUME_ML 1500 ///< Default fill volume for treatment in mL. +#define MAX_FILL_VOLUME_ML MAX_RESERVOIR_VOLUME_ML ///< Maximum fill volume in mL. +#define DEFAULT_DRAIN_VOLUME_ML 0 ///< Default drain volume in mL. +#define MAX_DRAIN_VOLUME_ML MAX_RESERVOIR_VOLUME_ML ///< Maximum drain volume in mL. -#define MAX_RESERVOIR_WEIGHT 10000 ///< Maximum reservoir weight in grams. - -#define RESERVOIR_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< interval (ms/task time) at which the reservoir data is published on the CAN bus. +#define MIN_DRAIN_INLET_PSI_EMPTY -3.0F ///< Minimum drain inlet pressure (in PSI) to indicate reservoir is empty while drain pump on. +#define RESERVOIR_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< interval (ms/task time) at which the reservoir data is published on the CAN bus. +#define MAX_REDUNDANT_LOAD_CELL_DIFF 50.0F ///< Maximum difference in redundant load cells when determining if fill completed. +#define MAX_DRAIN_RPM_MLP 2400.0F ///< Maximum drain RPM in mL/min. +#define DATA_PUBLISH_COUNTER_START_COUNT 5 ///< Data publish counter start count. -// ********** private data ********** +// ********** private data ********** + +/// Heaters temperature calculation data structure +typedef struct +{ + U32 timeReservoirCycleMS; ///< Time reservoir cycle in milliseconds. + U32 timeReservoirFill2SwitchMS; ///< Time reservoir fill to switch in milliseconds. + F32 timeUFDecayMS; ///< Time ultrafilter decay in milliseconds. + F32 timeReservoirFillMS; ///< Time reservoir fill in milliseconds. + F32 tempUFFill; ///< Temperature ultrafilter fill in C. + F32 tempReservoirUseActual; ///< Temperature actual reservoir in C. + F32 tempReservoir0; ///< Temperature reservoir at the beginning of fill in C. + F32 tempReservoirEndFill; ///< Temperature reservoir at the end of the fill in C. + F32 tempTargetTrimmer; ///< Temperature target trimmer heater in C. + F32 flowTargetDialysateLPM; ///< Dialysate target flow rate in L/min. +} HEATERS_TEMPERATURE_CALC_DATA_T; + +/// Reservoirs previous status +typedef struct +{ + F32 previousReservoirWeightG; ///< Previous reservoir weight in grams. + F32 previousDrainFlowML; ///< Previous reservoir drain flow in milliliters. +} RESERVOIRS_PREVIOUS_STATUS; + +static HEATERS_TEMPERATURE_CALC_DATA_T heatersTempCalc; ///< Heaters temperature calculations data structure. +static U32 dataPublishCounter; ///< used to schedule reservoir data publication to CAN bus. -static U32 reservoirDataPublicationTimerCounter = 0; ///< used to schedule reservoir data publication to CAN bus. - -static OVERRIDE_U32_T activeReservoir = { 0, 0, 0, 0 }; ///< The active reservoir that the DG is filling/draining/etc. -static OVERRIDE_U32_T fillVolumeTargetMl = { 0, 0, 0, 0 }; ///< The target reservoir fill volume (in mL). +static OVERRIDE_U32_T activeReservoir = { 0, 0, 0, 0 }; ///< The active reservoir that the DG is filling/draining/etc. +static OVERRIDE_U32_T fillVolumeTargetMl = { 0, 0, 0, 0 }; ///< The target reservoir fill volume (in mL). -static OVERRIDE_U32_T drainVolumeTargetMl = { 0, 0, 0, 0 }; ///< The target reservoir drain volume (in mL). +static OVERRIDE_U32_T drainVolumeTargetMl = { 0, 0, 0, 0 }; ///< The target reservoir drain volume (in mL). /// The reservoirs' associate load cell. static LOAD_CELL_ID_T associatedLoadCell[ NUM_OF_DG_RESERVOIRS ] = { LOAD_CELL_RESERVOIR_1_PRIMARY, LOAD_CELL_RESERVOIR_2_PRIMARY }; /// The reservoirs' associate redundant load cell. static LOAD_CELL_ID_T redundantLoadCell[ NUM_OF_DG_RESERVOIRS ] = { LOAD_CELL_RESERVOIR_1_BACKUP, LOAD_CELL_RESERVOIR_2_BACKUP }; /// The reservoirs' lowest weight during draining. -static F32 reservoirLowestWeight[ NUM_OF_DG_RESERVOIRS ] = { MAX_RESERVOIR_WEIGHT, MAX_RESERVOIR_WEIGHT }; -static U32 reservoirWeightUnchangeStartTime[ NUM_OF_DG_RESERVOIRS ] = { 0, 0 }; ///< The reservoirs' weight start time when weight stop decreasing. -static BOOL tareLoadCellRequest; ///< Flag indicates if load cell tare has been requested by HD. +static U32 reservoirWeightUnchangeStartTime[ NUM_OF_DG_RESERVOIRS ] = { 0, 0 }; ///< The reservoirs' weight start time when weight stop decreasing. +static BOOL tareLoadCellRequest; ///< Flag indicates if load cell tare has been requested by HD. +static DG_RESERVOIR_VOLUME_RECORD_T reservoirsCalRecord; ///< DG reservoirs non-volatile record. +static DG_HEATING_CAL_RECORD_T heatingConstsCalRecord; ///< DG heating calibration record. +static F32 targetFillFlowRateLPM; ///< Target fill flow rate in L/min. +static BOOL isThisTheFirstCycle = TRUE; ///< Boolean flag to indicate whether this is the first cycle. +static RESERVOIRS_PREVIOUS_STATUS reservoirPreviousStatus[ NUM_OF_DG_RESERVOIRS ]; ///< Reservoirs previous status. -// ********** private function prototypes ********** - -static DG_RESERVOIR_ID_T getActiveReservoir( void ); -static U32 getReservoirFillVolumeTargetMl( void ); - -static U32 getReservoirDrainVolumeTargetMl( void ); - /*********************************************************************//** * @brief * The initReservoirs function initializes the Reservoirs module. * @details Inputs: none - * @details Outputs: Reservoirs module initialized + * @details Outputs: activeReservoir.data, fillVolumeTargetMl.data, + * drainVolumeTargetMl.data, targetFillFlowRateLPM, isThisTheFirstCycle, + * previousReservoiWeight, dataPublishCounter * @return none *************************************************************************/ void initReservoirs( void ) { - activeReservoir.data = (U32)DG_RESERVOIR_1; - fillVolumeTargetMl.data = DEFAULT_FILL_VOLUME_ML; - drainVolumeTargetMl.data = DEFAULT_DRAIN_VOLUME_ML; + activeReservoir.data = (U32)DG_RESERVOIR_1; + fillVolumeTargetMl.data = DEFAULT_FILL_VOLUME_ML; + drainVolumeTargetMl.data = DEFAULT_DRAIN_VOLUME_ML; + targetFillFlowRateLPM = 0.0; + isThisTheFirstCycle = TRUE; + dataPublishCounter = DATA_PUBLISH_COUNTER_START_COUNT; + + memset( &reservoirPreviousStatus, 0.0, sizeof( RESERVOIRS_PREVIOUS_STATUS ) * NUM_OF_DG_RESERVOIRS ); } /*********************************************************************//** * @brief * The execReservoirs function manages periodic tasks for the Reservoirs module. - * @details Inputs: none - * @details Outputs: Reservoir data broadcast on interval + * @details Inputs: reservoirDataPublicationTimerCounter + * @details Outputs: reservoirDataPublicationTimerCounter, heatingCalRecord, + * reservoirsCalRecord * @return none *************************************************************************/ void execReservoirs( void ) -{ +{ + // Check if a new calibration is available + if ( TRUE == isNewCalibrationRecordAvailable() ) + { + // Get the new calibration data and check its validity + getNVRecord2Driver( GET_CAL_RSRVRS_VOL_RECORD, (U08*)&reservoirsCalRecord, sizeof( reservoirsCalRecord ), + NUM_OF_CAL_DATA_RSRVRS, ALARM_ID_DG_RESERVOIRS_INVALID_CAL_RECORD ); + + getNVRecord2Driver( GET_CAL_HEATING_RECORD, (U08*)&heatingConstsCalRecord, sizeof( heatingConstsCalRecord ), + NUM_OF_CAL_DATA_RSRVRS, ALARM_ID_DG_HEATING_INVALID_CAL_RECORD ); + } + // publish active reservoir, fill/drain volume targets at 1 Hz. - if ( ++reservoirDataPublicationTimerCounter >= RESERVOIR_DATA_PUB_INTERVAL ) - { - U32 actRes = getActiveReservoir(); - U32 filVol = getReservoirFillVolumeTargetMl(); - U32 drnVol = getReservoirDrainVolumeTargetMl(); - broadcastReservoirData( actRes, filVol, drnVol ); - reservoirDataPublicationTimerCounter = 0; + if ( ++dataPublishCounter >= RESERVOIR_DATA_PUB_INTERVAL ) + { + RESERVOIR_DATA_T data; + + data.activeReservoir = getU32OverrideValue( &activeReservoir ); + data.fillToVolumeMl = getU32OverrideValue( &fillVolumeTargetMl ); + data.drainToVolumeMl = getU32OverrideValue( &drainVolumeTargetMl ); + data.timeReservoirCycleMS = heatersTempCalc.timeReservoirCycleMS; + data.timeReservoirFill2SwitchMS = heatersTempCalc.timeReservoirFill2SwitchMS; + data.timeUFDecayMS = heatersTempCalc.timeUFDecayMS; + data.tempUFFill = heatersTempCalc.tempUFFill; + data.tempReservoirUseActual = getReservoirActualTemperature(); + data.tempReservoirEndFill = heatersTempCalc.tempReservoirEndFill; + data.tempAvgFill = getAvgFillTemperature(); + data.tempLastFill = getLastFillTemperature(); + data.timereservoirFill = heatersTempCalc.timeReservoirFillMS; + + broadcastData( MSG_ID_DG_RESERVOIRS_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&data, sizeof( RESERVOIR_DATA_T ) ); + dataPublishCounter = 0; } +} + +/*********************************************************************//** + * @brief + * The execDrainPumpSelfTest function executes the drain pump's self-test. + * @details Inputs: none + * @details Outputs: none + * @return PressuresSelfTestResult (SELF_TEST_STATUS_T) + *************************************************************************/ +SELF_TEST_STATUS_T execReservoirsSelfTest( void ) +{ + SELF_TEST_STATUS_T result = SELF_TEST_STATUS_IN_PROGRESS; + BOOL calStatus = FALSE; + + calStatus |= getNVRecord2Driver( GET_CAL_RSRVRS_VOL_RECORD, (U08*)&reservoirsCalRecord, sizeof( reservoirsCalRecord ), + NUM_OF_CAL_DATA_RSRVRS, ALARM_ID_DG_RESERVOIRS_INVALID_CAL_RECORD ); + + calStatus |= getNVRecord2Driver( GET_CAL_HEATING_RECORD, (U08*)&heatingConstsCalRecord, sizeof( heatingConstsCalRecord ), + NUM_OF_CAL_DATA_RSRVRS, ALARM_ID_DG_HEATING_INVALID_CAL_RECORD ); + + if ( TRUE == calStatus ) + { + result = SELF_TEST_STATUS_PASSED; + } + else + { + result = SELF_TEST_STATUS_FAILED; + } + + return result; } /*********************************************************************//** @@ -121,20 +209,15 @@ cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; - // switch reservoir command only valid in re-circulate mode - if ( DG_MODE_CIRC == getCurrentOperationMode() ) + // switch reservoir command only valid in generation idle mode + if ( DG_MODE_GENE == getCurrentOperationMode() ) { switch ( resID ) { case DG_RESERVOIR_1: activeReservoir.data = (U32)resID; cmdResponse.rejected = FALSE; setValveState( VRF, VALVE_STATE_R2_C_TO_NO ); -#ifndef V_2_SYSTEM - setValveState( VRD1, VALVE_STATE_CLOSED ); -#else - setValveState( VRD, VALVE_STATE_R2_C_TO_NO ); -#endif setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRI, VALVE_STATE_R1_C_TO_NO ); break; @@ -143,11 +226,6 @@ activeReservoir.data = (U32)resID; cmdResponse.rejected = FALSE; setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); -#ifndef V_2_SYSTEM - setValveState( VRD2, VALVE_STATE_CLOSED ); -#else - setValveState( VRD, VALVE_STATE_R1_C_TO_NC ); -#endif setValveState( VRO, VALVE_STATE_R2_C_TO_NC ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); break; @@ -183,8 +261,8 @@ cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; - // valve setting command only valid in re-circulate mode - if ( DG_MODE_CIRC == getCurrentOperationMode() ) + // valve setting command only valid in generation idle mode + if ( DG_MODE_GENE == getCurrentOperationMode() ) { switch ( valveSettingID ) { @@ -219,27 +297,33 @@ * The startFillCmd function handles a fill command from the HD. * @details Inputs: none * @details Outputs: move to fill mode - * @param fillToVolMl Target volume (in mL) to fill reservoir to + * @param fillToVolMl target volume (in mL) to fill reservoir to + * @param fillTargeteFlowLPM target fill flow rate in L/min * @return none *************************************************************************/ -void startFillCmd( U32 fillToVolMl ) +void startFillCmd( U32 fillToVolMl, F32 fillTargetLPM ) { DG_CMD_RESPONSE_T cmdResponse; - cmdResponse.commandID = DG_CMD_START_FILL; - cmdResponse.rejected = TRUE; + cmdResponse.commandID = DG_CMD_START_FILL; + cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; - // fill command only valid in re-circulate mode - if ( ( DG_MODE_CIRC == getCurrentOperationMode() ) && - ( DG_RECIRCULATE_MODE_STATE_RECIRC_WATER == getCurrentRecirculateState() ) ) + // fill command only valid in generation idle mode + if ( ( DG_MODE_GENE == getCurrentOperationMode() ) && ( ( DG_GEN_IDLE_MODE_STATE_FLUSH_WATER == getCurrentGenIdleState() ) || + ( DG_GEN_IDLE_MODE_STATE_HANDLE_BAD_FILL == getCurrentGenIdleState() ) ) ) { // validate parameters if ( fillToVolMl < MAX_FILL_VOLUME_ML ) { fillVolumeTargetMl.data = fillToVolMl; - requestNewOperationMode( DG_MODE_FILL ); - cmdResponse.rejected = FALSE; + cmdResponse.rejected = FALSE; + + if ( ( FALSE == isAlarmActive( ALARM_ID_DG_ACID_BOTTLE_LOW_VOLUME ) ) || // reject moving to fill mode if + ( FALSE == isAlarmActive( ALARM_ID_DG_BICARB_BOTTLE_LOW_VOLUME ) ) ) // alarm is active + { + requestNewOperationMode( DG_MODE_FILL ); + } } else { @@ -251,14 +335,17 @@ cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } + // Set the fill flow rate + targetFillFlowRateLPM = fillTargetLPM; + sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The stopFillCmd function handles a stop fill command from the HD. * @details Inputs: none - * @details Outputs: move to re-circulate mode + * @details Outputs: move to generation idle mode * @return none *************************************************************************/ void stopFillCmd( void ) @@ -272,8 +359,7 @@ // stop fill command only valid in fill mode if ( DG_MODE_FILL == getCurrentOperationMode() ) { - fillVolumeTargetMl.data = 0; - requestNewOperationMode( DG_MODE_CIRC ); + requestNewOperationMode( DG_MODE_GENE ); cmdResponse.rejected = FALSE; } else @@ -288,7 +374,7 @@ * @brief * The startDrainCmd function handles a drain command from the HD. * @details Inputs: none - * @details Outputs: Start draining in re-circulate mode + * @details Outputs: Start draining in generation idle mode * @param drainCmd drain command data record * @return none *************************************************************************/ @@ -300,14 +386,15 @@ cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; - // drain command only valid in re-circulate mode - if ( DG_MODE_CIRC == getCurrentOperationMode() ) + // drain command only valid in generation idle mode + if ( DG_MODE_GENE == getCurrentOperationMode() ) { // validate parameters if ( drainCmd.targetVolume <= MAX_DRAIN_VOLUME_ML ) { drainVolumeTargetMl.data = drainCmd.targetVolume; tareLoadCellRequest = drainCmd.tareLoadCell; + signalDrainModeRinseConcentrateLines( drainCmd.rinseConcentrateLines ); requestNewOperationMode( DG_MODE_DRAI ); cmdResponse.rejected = FALSE; } @@ -328,7 +415,7 @@ * @brief * The stopDrainCmd function handles a stop drain command from the HD. * @details Inputs: none - * @details Outputs: move to re-circulate mode + * @details Outputs: move to generation idle mode * @return none *************************************************************************/ void stopDrainCmd( void ) @@ -343,7 +430,7 @@ if ( DG_MODE_DRAI == getCurrentOperationMode() ) { drainVolumeTargetMl.data = 0; - requestNewOperationMode( DG_MODE_CIRC ); + requestNewOperationMode( DG_MODE_GENE ); cmdResponse.rejected = FALSE; } else @@ -356,61 +443,203 @@ /*********************************************************************//** * @brief - * The tareReservoir function sets the tare load cell variable to TRUE. + * The getInactiveReservoir function gets the inactive reservoir. + * @details Inputs: activeReservoir + * @details Outputs: none + * @return the currently inactive reservoir. + *************************************************************************/ +DG_RESERVOIR_ID_T getInactiveReservoir( void ) +{ + DG_RESERVOIR_ID_T inactiveReservoir = DG_RESERVOIR_1; + + if ( DG_RESERVOIR_1 == getU32OverrideValue( &activeReservoir ) ) + { + inactiveReservoir = DG_RESERVOIR_2; + } + + return inactiveReservoir; +} + +/*********************************************************************//** + * @brief + * The getTargetDialysateFlowLPM function returns the target dialysate flow + * rate in L/min. * @details Inputs: none + * @details Outputs: heatersTempCalc + * @return target dialysate flow rate in L/min + *************************************************************************/ +F32 getTargetDialysateFlowLPM( void ) +{ + return heatersTempCalc.flowTargetDialysateLPM; +} + +/*********************************************************************//** + * @brief + * The getReservoirWeight function returns the small filtered weight + * of the reservoir's associated load cell. + * @details Inputs: none * @details Outputs: none - * @return none + * @param reservoirId id of reservoir to get weight from + * @return small filtered weight *************************************************************************/ -void tareReservoir( void ) +F32 getReservoirWeight( DG_RESERVOIR_ID_T reservoirId ) { - tareLoadCellRequest = TRUE; + return getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); } /*********************************************************************//** * @brief - * The resetReservoirsLowestWeight function resets the lowest load cell - * weight of the reservoirs. - * @details Inputs: reservoirLowestWeight - * @details Outputs: reservoirLowestWeight - * @return none + * The getTargetFillVolumeML function returns the target fill volume in mL. + * @details Inputs: none + * @details Outputs: none + * @return target fill volume in mL *************************************************************************/ -void resetReservoirsLowestWeight( void ) +U32 getTargetFillVolumeML( void ) { - reservoirLowestWeight[ DG_RESERVOIR_1 ] = MAX_RESERVOIR_WEIGHT; - reservoirLowestWeight[ DG_RESERVOIR_2 ] = MAX_RESERVOIR_WEIGHT; + U32 targetFill = fillVolumeTargetMl.data; + + if ( OVERRIDE_KEY == fillVolumeTargetMl.override ) + { + targetFill = fillVolumeTargetMl.ovData; + } + + return targetFill; } /*********************************************************************//** * @brief - * The getInactiveReservoir function gets the inactive reservoir. - * @details Inputs: activeReservoir + * The getTargetFillFlowRateLPM function returns the target fill flow rate + * in L/min. + * @details Inputs: none * @details Outputs: none - * @return the currently inactive reservoir. + * @return target fill flow rate in L/min *************************************************************************/ -DG_RESERVOIR_ID_T getInactiveReservoir( void ) +F32 getTargetFillFlowRateLPM( void ) { - DG_RESERVOIR_ID_T inactiveReservoir = DG_RESERVOIR_1; + return targetFillFlowRateLPM; +} - if ( DG_RESERVOIR_1 == getActiveReservoir() ) +/*********************************************************************//** + * @brief + * The setDialysateHeatingParameters function sets the dialysate heating + * parameters. + * @details Inputs: none + * @details Outputs: heatersTempCalc + * @return none + *************************************************************************/ +void setDialysateHeatingParameters( DG_CMD_DIALYSATE_HEATING_PARAMS_T params ) +{ + heatersTempCalc.timeReservoirCycleMS = params.timeReservoirCycleMS; + heatersTempCalc.timeReservoirFill2SwitchMS = params.timeReservoirWait2SwitchMS; + heatersTempCalc.timeReservoirFillMS = params.timeReservoirFillMS; + heatersTempCalc.tempTargetTrimmer = params.trimmerTargetTemperature; + heatersTempCalc.flowTargetDialysateLPM = params.dialysateFlowLPM; + + // Set the trimmer heater target temperature since this value is needed for calculations + setHeaterTargetTemperature( DG_TRIMMER_HEATER, heatersTempCalc.tempTargetTrimmer ); + + // Check if this is the first time that the dialysate heating parameters are set in DG + if ( TRUE == isThisTheFirstCycle ) { - inactiveReservoir = DG_RESERVOIR_2; + resetFillStatusParameters(); + isThisTheFirstCycle = FALSE; } +} - return inactiveReservoir; +/*********************************************************************//** + * @brief + * The getReservoirActualTemperature function calculates the reservoir's + * actual temperature. + * @details Inputs: none + * @details Outputs: heatersTempCalc + * @return reservoir actual temperature + *************************************************************************/ +F32 getReservoirActualTemperature( void ) +{ + F32 UFTimeConstant = 0.0; + F32 targetFillVolML = getTargetFillVolumeML(); + F32 tempLastFill = getLastFillTemperature(); + F32 tempAvgFill = getAvgFillTemperature(); + F32 UFTauCPerMS = heatingConstsCalRecord.ultrafilterTempTauCPerMin / ( SEC_PER_MIN * MS_PER_SECOND ); + F32 RsrvrTauCPerMS = heatingConstsCalRecord.reservoirTempTauCPerMin / ( SEC_PER_MIN * MS_PER_SECOND ); + + // Only do the calculations if the fill volume is not 0.0 so the final value will not be a nan. + if ( targetFillVolML > NEARLY_ZERO ) + { + heatersTempCalc.timeUFDecayMS = (F32)heatersTempCalc.timeReservoirCycleMS - heatersTempCalc.timeReservoirFillMS; + UFTimeConstant = heatersTempCalc.timeUFDecayMS * UFTauCPerMS; + heatersTempCalc.tempUFFill = tempLastFill + UFTimeConstant; + + F32 ultrafilterPart = ( heatingConstsCalRecord.ultrafilterVolmL / targetFillVolML ) * heatersTempCalc.tempUFFill; + F32 fillPart = ( ( targetFillVolML - heatingConstsCalRecord.ultrafilterVolmL ) / targetFillVolML ) * tempAvgFill; + F32 tempReservoir0Actual = ultrafilterPart + fillPart; + + F32 tempReservoirEndfillActual = tempReservoir0Actual + ( ( heatersTempCalc.timeReservoirFillMS * 0.5 ) * RsrvrTauCPerMS ); + heatersTempCalc.tempReservoirUseActual = tempReservoirEndfillActual + ( heatersTempCalc.timeReservoirFill2SwitchMS * RsrvrTauCPerMS ); + } + else + { + heatersTempCalc.tempReservoirUseActual = 0.0; + } + + return heatersTempCalc.tempReservoirUseActual; } /*********************************************************************//** * @brief - * The getReservoirWeight function returns the large filtered weight - * of the reservoir's associated load cell. - * @details Inputs: associatedLoadCell[] + * The getPrimaryHeaterTargetTemperature function calculates the primary + * heater target temperature and returns target temperature value. + * @details Inputs: none + * @details Outputs: heatersTempCalc + * @return primary heater target temperature + *************************************************************************/ +F32 getPrimaryHeaterTargetTemperature( void ) +{ + // TODO once the equations are solidified, add the equations as comments to the lines + F32 tempTarget = 0.0; + F32 targetFillVolML = getTargetFillVolumeML(); + F32 UFTimeConstant = 0.0; + F32 tempLastFill = getLastFillTemperature(); + F32 UFTauCPerMS = heatingConstsCalRecord.ultrafilterTempTauCPerMin / ( SEC_PER_MIN * MS_PER_SECOND ); + F32 RsrvrTauCPerMS = heatingConstsCalRecord.reservoirTempTauCPerMin / ( SEC_PER_MIN * MS_PER_SECOND ); + + if ( FALSE == isThisTheFirstFill() ) + { + F32 tempTargetNumerator; + F32 targetTempDenominator; + F32 tempReservoirUse; + + tempReservoirUse = heatersTempCalc.tempTargetTrimmer + RESERVOIR_EXTRA_TEMPERATURE; + heatersTempCalc.tempReservoirEndFill = tempReservoirUse - ( heatersTempCalc.timeReservoirFill2SwitchMS * RsrvrTauCPerMS ); + heatersTempCalc.tempReservoir0 = heatersTempCalc.tempReservoirEndFill - ( ( heatersTempCalc.timeReservoirFillMS * 0.5 ) * RsrvrTauCPerMS ); + + heatersTempCalc.timeUFDecayMS = (F32)heatersTempCalc.timeReservoirCycleMS - heatersTempCalc.timeReservoirFillMS; + UFTimeConstant = heatersTempCalc.timeUFDecayMS * UFTauCPerMS; + heatersTempCalc.tempUFFill = tempLastFill + UFTimeConstant; + + tempTargetNumerator = heatersTempCalc.tempReservoir0 - ( ( heatingConstsCalRecord.ultrafilterVolmL / targetFillVolML ) * heatersTempCalc.tempUFFill ); + targetTempDenominator = ( ( targetFillVolML - heatingConstsCalRecord.ultrafilterVolmL ) / targetFillVolML ); + tempTarget = tempTargetNumerator / targetTempDenominator; + } + else + { + tempTarget = heatersTempCalc.tempTargetTrimmer + RESERVOIR_EXTRA_TEMPERATURE; + } + + return tempTarget; +} + +/*********************************************************************//** + * @brief + * The getReservoirsCalRecord function returns the reservoirs' calibration + * record. + * @details Inputs: reservoirsCalRecord * @details Outputs: none - * @param reservoirId id of reservoir to get weight from - * @return large filtered weight + * @return reservoirs' calibration record *************************************************************************/ -F32 getReservoirWeight( DG_RESERVOIR_ID_T reservoirId ) +DG_RESERVOIR_VOLUME_RECORD_T getReservoirsCalRecord( void ) { - return getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); + return reservoirsCalRecord; } /*********************************************************************//** @@ -424,19 +653,27 @@ *************************************************************************/ BOOL hasTargetFillVolumeBeenReached( DG_RESERVOIR_ID_T reservoirId ) { - F32 const loadcellWeight = getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); - U32 const targetFillVolume = getReservoirFillVolumeTargetMl(); - BOOL const hasTargetReached = ( loadcellWeight >= targetFillVolume ); + F32 loadcellWeight1 = getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); + F32 loadcellWeight2 = getLoadCellSmallFilteredWeight( redundantLoadCell[ reservoirId ] ); + U32 targetFillVolume = getU32OverrideValue( &fillVolumeTargetMl ); + BOOL hasTargetReached = ( ( loadcellWeight1 >= targetFillVolume || loadcellWeight2 > ( targetFillVolume + MAX_REDUNDANT_LOAD_CELL_DIFF ) ) ? TRUE : FALSE ); + // if redundant load cells too far apart at end of fill, alarm + if ( loadcellWeight1 < targetFillVolume ) + { + // TODO - alarm + } + return hasTargetReached; } /*********************************************************************//** * @brief * The hasTargetDrainVolumeReached function checks if the target drain volume * for specific reservoir has been reached or exceed time limit. - * @details Inputs: drainVolumeTargetMl - * @details Outputs: none + * @details Inputs: reservoirWeightUnchangeStartTime, reservoirWeightUnchangeStartTime + * @details Outputs: reservoirWeightUnchangeStartTime, tareLoadCellRequest, + * reservoirWeightUnchangeStartTime * @param reservoirId reservoir id * @param timeout timeout period when weight remains the same * @return TRUE if target drain volume has been reached or exceeds time limit, FALSE if not. @@ -445,32 +682,48 @@ { BOOL result = FALSE; - F32 const loadcellWeight = getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); - U32 const targetDrainVolume = getReservoirDrainVolumeTargetMl(); + F32 loadcellWeightML = getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); + U32 drainPumpFeedbackRPM = getDrainPumpMeasuredRPM(); - if ( loadcellWeight < reservoirLowestWeight[ reservoirId ] ) + // Check if the drain pump is running, the drain pump usually starts with delay so it might take several + // milliseconds to see the RPM feedback is greater than 0. If the feedback is not greater than 0, checking + // for drain is not needed + if ( drainPumpFeedbackRPM > 0 ) { - reservoirLowestWeight[ reservoirId ] = loadcellWeight; - reservoirWeightUnchangeStartTime[ reservoirId ] = getMSTimerCount(); - } + // Calculate the drain threshold that is needed for the target RPM. For instance, RPM = 500 / 2400 as reference = 0.20833 + // so the difference in between the current and previous load cell values must be less than this value to be considered + // as reaching towards the bottom of the reservoir. Otherwise, the drain pump must be easily able to drain more fluid + // than the threshold every 50 ms. + F32 drainThresholdML = ( (F32)getDrainPumpTargetRPM() ) / MAX_DRAIN_RPM_MLP; - BOOL const hasTimeOut = didTimeout( reservoirWeightUnchangeStartTime[ reservoirId ], timeout ); - BOOL const hasTargetReached = ( targetDrainVolume >= loadcellWeight ); + // Calculate the flow + reservoirPreviousStatus[ reservoirId ].previousDrainFlowML = reservoirPreviousStatus[ reservoirId ].previousReservoirWeightG - loadcellWeightML; - // If the goal is to tare the load cell, then the target drain should be reached and timing out on the - // reservoir weight is not enough - if ( ( TRUE == hasTimeOut ) || ( ( TRUE == hasTargetReached ) && ( FALSE == tareLoadCellRequest ) ) ) - { - result = TRUE; - // Reset for next drain - reservoirLowestWeight[ reservoirId ] = MAX_RESERVOIR_WEIGHT; + // If the previous load cell is greater than the current load cell, it means the reservoir is draining and + // update the previous load cell value + if ( reservoirPreviousStatus[ reservoirId ].previousReservoirWeightG > loadcellWeightML ) + { + reservoirPreviousStatus[ reservoirId ].previousReservoirWeightG = loadcellWeightML; + } - if ( TRUE == tareLoadCellRequest ) + // If the flow is less than the threshold and the time has not been set, set the timer + // If the flow is greater than the threshold and timer has been set, 0 it because the flow is out of range and we are not + // ready to consider this the end of the reservoir flow + // If the wait for drain to steady has elapsed and the drain pump inlet pressure sensor is indicating and increased vacuum, + // signal the drain is complete + if ( ( reservoirPreviousStatus[ reservoirId ].previousDrainFlowML <= drainThresholdML ) && ( 0 == reservoirWeightUnchangeStartTime[ reservoirId ] ) ) { - tareLoadCellRequest = FALSE; - tareLoadCell( associatedLoadCell[ reservoirId ] ); - tareLoadCell( redundantLoadCell[ reservoirId ] ); + reservoirWeightUnchangeStartTime[ reservoirId ] = getMSTimerCount(); } + else if ( reservoirPreviousStatus[ reservoirId ].previousDrainFlowML > drainThresholdML ) + { + reservoirWeightUnchangeStartTime[ reservoirId ] = 0; + } + else if ( ( TRUE == didTimeout( reservoirWeightUnchangeStartTime[ reservoirId ], timeout ) && + ( getMeasuredDGPressure( PRESSURE_SENSOR_DRAIN_PUMP_INLET ) > MIN_DRAIN_INLET_PSI_EMPTY ) ) ) + { + result = TRUE; + } } return result; @@ -487,7 +740,7 @@ *************************************************************************/ void tareLoadCellsAtEmpty( DG_RESERVOIR_ID_T reservoirId ) { - U32 const targetDrainVolume = getReservoirDrainVolumeTargetMl(); + U32 const targetDrainVolume = getU32OverrideValue( &drainVolumeTargetMl ); if ( TRUE == tareLoadCellRequest ) { @@ -516,62 +769,23 @@ /*********************************************************************//** * @brief - * The getActiveReservoir function gets the active reservoir. - * @details Inputs: activeReservoir - * @details Outputs: none - * @return the currently active reservoir. + * The initDrainParameters function initializes the drain parameters. + * @details Inputs: none + * @details Outputs: reservoirWeightUnchangeStartTime, previousReservoirWeight + * associatedLoadCell + * @param reservoirId the ID of the reservoir that it drain parameters have + * to be initialized + * @return none *************************************************************************/ -static DG_RESERVOIR_ID_T getActiveReservoir( void ) +void initDrainParameters( DG_RESERVOIR_ID_T reservoirId ) { - DG_RESERVOIR_ID_T result = (DG_RESERVOIR_ID_T)activeReservoir.data; - - if ( OVERRIDE_KEY == activeReservoir.override ) - { - result = (DG_RESERVOIR_ID_T)activeReservoir.ovData; - } - - return result; + // Set the start time to 0 for the next drain and update the current load cell reading to the previous value + reservoirWeightUnchangeStartTime[ reservoirId ] = 0; + reservoirPreviousStatus[ reservoirId ].previousReservoirWeightG = getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); + reservoirPreviousStatus[ reservoirId ].previousDrainFlowML = 0.0; } -/*********************************************************************//** - * @brief - * The getReservoirFillVolumeTargetMl function gets the reservoir fill volume (in mL). - * @details Inputs: fillVolumeTargetMl - * @details Outputs: none - * @return the current target reservoir fill volume (in mL). - *************************************************************************/ -static U32 getReservoirFillVolumeTargetMl( void ) -{ - U32 result = fillVolumeTargetMl.data; - if ( OVERRIDE_KEY == fillVolumeTargetMl.override ) - { - result = fillVolumeTargetMl.ovData; - } - - return result; -} - -/*********************************************************************//** - * @brief - * The getReservoirDrainVolumeTargetMl function gets the reservoir drain volume (in mL). - * @details Inputs: drainVolumeTargetMl - * @details Outputs: none - * @return the current target reservoir drain volume (in mL). - *************************************************************************/ -static U32 getReservoirDrainVolumeTargetMl( void ) -{ - U32 result = drainVolumeTargetMl.data; - - if ( OVERRIDE_KEY == drainVolumeTargetMl.override ) - { - result = drainVolumeTargetMl.ovData; - } - - return result; -} - - /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ @@ -591,8 +805,8 @@ if ( TRUE == isTestingActivated() ) { - result = TRUE; - activeReservoir.ovData = value; + result = TRUE; + activeReservoir.ovData = value; activeReservoir.override = OVERRIDE_KEY; } @@ -612,9 +826,9 @@ if ( TRUE == isTestingActivated() ) { - result = TRUE; + result = TRUE; activeReservoir.override = OVERRIDE_RESET; - activeReservoir.ovData = activeReservoir.ovInitData; + activeReservoir.ovData = activeReservoir.ovInitData; } return result; @@ -635,8 +849,8 @@ if ( TRUE == isTestingActivated() ) { - result = TRUE; - fillVolumeTargetMl.ovData = value; + result = TRUE; + fillVolumeTargetMl.ovData = value; fillVolumeTargetMl.override = OVERRIDE_KEY; } @@ -657,9 +871,9 @@ if ( TRUE == isTestingActivated() ) { - result = TRUE; + result = TRUE; fillVolumeTargetMl.override = OVERRIDE_RESET; - fillVolumeTargetMl.ovData = fillVolumeTargetMl.ovInitData; + fillVolumeTargetMl.ovData = fillVolumeTargetMl.ovInitData; } return result; @@ -710,4 +924,32 @@ return result; } +/*********************************************************************//** + * @brief + * The testTareReservoir function tares a given reservoir. It is assumed + * that the given reservoir has already been drained. + * @details Inputs: drainVolumeTargetMl + * @details Outputs: drainVolumeTargetMl + * @param value ID of reservoir to tare + * @return TRUE if tare successful, FALSE if not + *************************************************************************/ +BOOL testTareReservoir( U32 value ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + if ( value < NUM_OF_DG_RESERVOIRS ) + { + result = TRUE; + tareLoadCellRequest = TRUE; + testSetReservoirDrainVolumeMlOverride( 0 ); + tareLoadCellsAtEmpty( (DG_RESERVOIR_ID_T)value ); + testResetReservoirDrainVolumeMlOverride(); + } + } + + return result; +} + /**@}*/