Index: firmware/App/Services/Reservoirs.c =================================================================== diff -u -r1aeab08c1baf6445514b81fe51fc60a3e536e782 -reeb4e9c7c8ca2bc41168353c3d30f311080972b2 --- firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision 1aeab08c1baf6445514b81fe51fc60a3e536e782) +++ firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision eeb4e9c7c8ca2bc41168353c3d30f311080972b2) @@ -17,9 +17,11 @@ #include // for memcpy() +#include "Heaters.h" #include "LoadCell.h" #include "MessageSupport.h" #include "ModeDrain.h" +#include "ModeFill.h" #include "ModeGenIdle.h" #include "OperationModes.h" #include "Pressures.h" @@ -36,28 +38,48 @@ // ********** 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 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 MAX_RESERVOIR_WEIGHT 10000 ///< Maximum reservoir weight in grams. -#define MIN_DRAIN_INLET_PSI_EMPTY -3.0 ///< Minimum drain inlet pressure (in PSI) to indicate reservoir is empty while drain pump on. +#define MIN_DRAIN_INLET_PSI_EMPTY -3.0 ///< 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 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.0 ///< Maximum difference in redundant load cells when determing if fill completed. +#define MAX_REDUNDANT_LOAD_CELL_DIFF 50.0 ///< Maximum difference in redundant load cells when determining if fill completed. -// ********** private data ********** +#define RESERVOIR_TEMPERATURE_TAU_C_PER_MIN -0.512 ///< Reservoir temperature time constant C/min. +#define ULTRAFILTER_TEMPERATURE_TAU_C_PER_MIN -4.565 ///< Ultrafilter temperature time constant C/min. +#define ULTRAFILTER_VOLUME_ML 700 ///< Ultrafilter volume in milliliters. + +// ********** 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; + +static HEATERS_TEMPERATURE_CALC_DATA_T heatersTempCalc; ///< Heaters temperature calculations data structure. +static U32 reservoirDataPublicationTimerCounter = 0; ///< 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 }; @@ -66,26 +88,37 @@ /// 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 DG_RESERVOIR_VOLUME_RECORD_T reservoirsCalRecord; ///< DG reservoirs non-volatile record. +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 F32 targetFillFlowRateLPM; ///< Target fill flow rate in L/min. +static BOOL isThisTheFirstCycle = TRUE; ///< Boolean flag to indicate whether this is the first cycle. +/// Conversion of ultrafilter tau in C/min to C/ms. +static const F32 ULTRAFILTER_TAU_C_PER_MS = ULTRAFILTER_TEMPERATURE_TAU_C_PER_MIN / ( SEC_PER_MIN * MS_PER_SECOND ); + +/// Conversion of reservoir tau in C/min to C/ms. +static const F32 RESERVOIR_TAU_C_PER_MS = RESERVOIR_TEMPERATURE_TAU_C_PER_MIN / ( SEC_PER_MIN * MS_PER_SECOND ); + // ********** private function prototypes ********** -static BOOL processCalibrationData( void ); +static BOOL processCalibrationData( 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 * @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; } /*********************************************************************//** @@ -109,11 +142,20 @@ { RESERVOIR_DATA_T data; - data.activeReservoir = getU32OverrideValue( &activeReservoir ); - data.fillToVolumeMl = getU32OverrideValue( &fillVolumeTargetMl ); - data.drainToVolumeMl = getU32OverrideValue( &drainVolumeTargetMl ); + 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_RESERVOIR_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&data, sizeof( RESERVOIR_DATA_T ) ); + broadcastData( MSG_ID_DG_RESERVOIRS_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&data, sizeof( RESERVOIR_DATA_T ) ); reservoirDataPublicationTimerCounter = 0; } } @@ -248,27 +290,28 @@ * 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 generation idle mode - if ( ( DG_MODE_GENE == getCurrentOperationMode() ) && - ( DG_GEN_IDLE_MODE_STATE_FLUSH_WATER == getCurrentGenIdleState() ) ) + if ( ( DG_MODE_GENE == getCurrentOperationMode() ) && ( DG_GEN_IDLE_MODE_STATE_FLUSH_WATER == getCurrentGenIdleState() ) ) { // validate parameters if ( fillToVolMl < MAX_FILL_VOLUME_ML ) { fillVolumeTargetMl.data = fillToVolMl; + cmdResponse.rejected = FALSE; + requestNewOperationMode( DG_MODE_FILL ); - cmdResponse.rejected = FALSE; } else { @@ -280,6 +323,9 @@ cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } + // Set the fill flow rate + targetFillFlowRateLPM = fillTargetLPM; + sendCommandResponseMsg( &cmdResponse ); } @@ -301,7 +347,6 @@ // stop fill command only valid in fill mode if ( DG_MODE_FILL == getCurrentOperationMode() ) { - fillVolumeTargetMl.data = 0; requestNewOperationMode( DG_MODE_GENE ); cmdResponse.rejected = FALSE; } @@ -431,9 +476,22 @@ /*********************************************************************//** * @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: associatedLoadCell[] + * @details Inputs: none * @details Outputs: none * @param reservoirId id of reservoir to get weight from * @return small filtered weight @@ -445,6 +503,144 @@ /*********************************************************************//** * @brief + * The getTargetFillVolumeML function returns the target fill volume in mL. + * @details Inputs: none + * @details Outputs: none + * @return target fill volume in mL + *************************************************************************/ +U32 getTargetFillVolumeML( void ) +{ + U32 targetFill = fillVolumeTargetMl.data; + + if ( OVERRIDE_KEY == fillVolumeTargetMl.override ) + { + targetFill = fillVolumeTargetMl.ovData; + } + + return targetFill; +} + +/*********************************************************************//** + * @brief + * The getTargetFillFlowRateLPM function returns the target fill flow rate + * in L/min. + * @details Inputs: none + * @details Outputs: none + * @return target fill flow rate in L/min + *************************************************************************/ +F32 getTargetFillFlowRateLPM( void ) +{ + return targetFillFlowRateLPM; +} + +/*********************************************************************//** + * @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 ) + { + resetFillStatusParameters(); + isThisTheFirstCycle = FALSE; + } +} + +/*********************************************************************//** + * @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(); + + // 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 * ULTRAFILTER_TAU_C_PER_MS; + heatersTempCalc.tempUFFill = tempLastFill + UFTimeConstant; + + F32 ultrafilterPart = ( ULTRAFILTER_VOLUME_ML / targetFillVolML ) * heatersTempCalc.tempUFFill; + F32 fillPart = ( ( targetFillVolML - ULTRAFILTER_VOLUME_ML ) / targetFillVolML ) * tempAvgFill; + F32 tempReservoir0Actual = ultrafilterPart + fillPart; + + F32 tempReservoirEndfillActual = tempReservoir0Actual + ( ( heatersTempCalc.timeReservoirFillMS * 0.5 ) * RESERVOIR_TAU_C_PER_MS ); + heatersTempCalc.tempReservoirUseActual = tempReservoirEndfillActual + ( heatersTempCalc.timeReservoirFill2SwitchMS * RESERVOIR_TAU_C_PER_MS ); + } + else + { + heatersTempCalc.tempReservoirUseActual = 0.0; + } + + return heatersTempCalc.tempReservoirUseActual; +} + +/*********************************************************************//** + * @brief + * 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(); + + if ( FALSE == isThisTheFirstFill() ) + { + F32 tempTargetNumerator; + F32 targetTempDenominator; + F32 tempReservoirUse; + + tempReservoirUse = heatersTempCalc.tempTargetTrimmer + RESERVOIR_EXTRA_TEMPERATURE; + heatersTempCalc.tempReservoirEndFill = tempReservoirUse - ( heatersTempCalc.timeReservoirFill2SwitchMS * RESERVOIR_TAU_C_PER_MS ); + heatersTempCalc.tempReservoir0 = heatersTempCalc.tempReservoirEndFill - ( ( heatersTempCalc.timeReservoirFillMS * 0.5 ) * RESERVOIR_TAU_C_PER_MS ); + + heatersTempCalc.timeUFDecayMS = (F32)heatersTempCalc.timeReservoirCycleMS - heatersTempCalc.timeReservoirFillMS; + UFTimeConstant = heatersTempCalc.timeUFDecayMS * ULTRAFILTER_TAU_C_PER_MS; + heatersTempCalc.tempUFFill = tempLastFill + UFTimeConstant; + + tempTargetNumerator = heatersTempCalc.tempReservoir0 - ( ( ULTRAFILTER_VOLUME_ML / targetFillVolML ) * heatersTempCalc.tempUFFill ); + targetTempDenominator = ( ( targetFillVolML - ULTRAFILTER_VOLUME_ML ) / 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 @@ -467,10 +663,10 @@ *************************************************************************/ BOOL hasTargetFillVolumeBeenReached( DG_RESERVOIR_ID_T reservoirId ) { - F32 const loadcellWeight1 = getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); - F32 const loadcellWeight2 = getLoadCellSmallFilteredWeight( redundantLoadCell[ reservoirId ] ); - U32 const targetFillVolume = getU32OverrideValue( &fillVolumeTargetMl ); - BOOL const hasTargetReached = ( ( loadcellWeight1 >= targetFillVolume || loadcellWeight2 > ( targetFillVolume + MAX_REDUNDANT_LOAD_CELL_DIFF ) ) ? TRUE : FALSE ); + 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 ) @@ -618,8 +814,8 @@ if ( TRUE == isTestingActivated() ) { - result = TRUE; - activeReservoir.ovData = value; + result = TRUE; + activeReservoir.ovData = value; activeReservoir.override = OVERRIDE_KEY; } @@ -639,9 +835,9 @@ if ( TRUE == isTestingActivated() ) { - result = TRUE; + result = TRUE; activeReservoir.override = OVERRIDE_RESET; - activeReservoir.ovData = activeReservoir.ovInitData; + activeReservoir.ovData = activeReservoir.ovInitData; } return result; @@ -662,8 +858,8 @@ if ( TRUE == isTestingActivated() ) { - result = TRUE; - fillVolumeTargetMl.ovData = value; + result = TRUE; + fillVolumeTargetMl.ovData = value; fillVolumeTargetMl.override = OVERRIDE_KEY; } @@ -684,9 +880,9 @@ if ( TRUE == isTestingActivated() ) { - result = TRUE; + result = TRUE; fillVolumeTargetMl.override = OVERRIDE_RESET; - fillVolumeTargetMl.ovData = fillVolumeTargetMl.ovInitData; + fillVolumeTargetMl.ovData = fillVolumeTargetMl.ovInitData; } return result;