Index: firmware/App/Services/Reservoirs.c =================================================================== diff -u -r025612ad77fe630889a364586de54bffe5262d56 -r321e3603df2611c653e4bd330f640ab35822fd6d --- firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision 025612ad77fe630889a364586de54bffe5262d56) +++ firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision 321e3603df2611c653e4bd330f640ab35822fd6d) @@ -17,6 +17,7 @@ #include // for memcpy() +#include "DrainPump.h" #include "Heaters.h" #include "LoadCell.h" #include "MessageSupport.h" @@ -44,18 +45,12 @@ #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 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 MAX_REDUNDANT_LOAD_CELL_DIFF 50.0 ///< Maximum difference in redundant load cells when determining if fill completed. +#define MAX_DRAIN_RPM_MLP 2400.0 ///< Maximum drain RPM in mL/min. +#define DATA_PUBLISH_COUNTER_START_COUNT 5 ///< Data publish counter start count. -#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 @@ -71,10 +66,17 @@ 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; +} 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 reservoirDataPublicationTimerCounter = 0; ///< used to schedule reservoir data publication to CAN bus. +static U32 dataPublishCounter; ///< 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). @@ -87,29 +89,21 @@ 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 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. -/// 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 ); - /*********************************************************************//** * @brief * The initReservoirs function initializes the Reservoirs module. * @details Inputs: none * @details Outputs: activeReservoir.data, fillVolumeTargetMl.data, - * drainVolumeTargetMl.data, targetFillFlowRateLPM, isThisTheFirstCycle + * drainVolumeTargetMl.data, targetFillFlowRateLPM, isThisTheFirstCycle, + * previousReservoiWeight, dataPublishCounter * @return none *************************************************************************/ void initReservoirs( void ) @@ -119,13 +113,17 @@ 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: reservoirDataPublicationTimerCounter - * @details Outputs: reservoirDataPublicationTimerCounter + * @details Outputs: reservoirDataPublicationTimerCounter, heatingCalRecord, + * reservoirsCalRecord * @return none *************************************************************************/ void execReservoirs( void ) @@ -134,11 +132,15 @@ if ( TRUE == isNewCalibrationRecordAvailable() ) { // Get the new calibration data and check its validity - processCalibrationData(); + 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 ) + if ( ++dataPublishCounter >= RESERVOIR_DATA_PUB_INTERVAL ) { RESERVOIR_DATA_T data; @@ -156,7 +158,7 @@ data.timereservoirFill = heatersTempCalc.timeReservoirFillMS; broadcastData( MSG_ID_DG_RESERVOIRS_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&data, sizeof( RESERVOIR_DATA_T ) ); - reservoirDataPublicationTimerCounter = 0; + dataPublishCounter = 0; } } @@ -170,9 +172,14 @@ SELF_TEST_STATUS_T execReservoirsSelfTest( void ) { SELF_TEST_STATUS_T result = SELF_TEST_STATUS_IN_PROGRESS; + BOOL calStatus = FALSE; - BOOL calStatus = processCalibrationData(); + 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; @@ -431,32 +438,6 @@ /*********************************************************************//** * @brief - * The tareReservoir function sets the tare load cell variable to TRUE. - * @details Inputs: none - * @details Outputs: none - * @return none - *************************************************************************/ -void tareReservoir( void ) -{ - tareLoadCellRequest = TRUE; -} - -/*********************************************************************//** - * @brief - * The resetReservoirsLowestWeight function resets the lowest load cell - * weight of the reservoirs. - * @details Inputs: reservoirLowestWeight - * @details Outputs: reservoirLowestWeight - * @return none - *************************************************************************/ -void resetReservoirsLowestWeight( void ) -{ - reservoirLowestWeight[ DG_RESERVOIR_1 ] = MAX_RESERVOIR_WEIGHT; - reservoirLowestWeight[ DG_RESERVOIR_2 ] = MAX_RESERVOIR_WEIGHT; -} - -/*********************************************************************//** - * @brief * The getInactiveReservoir function gets the inactive reservoir. * @details Inputs: activeReservoir * @details Outputs: none @@ -574,20 +555,22 @@ 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 * ULTRAFILTER_TAU_C_PER_MS; + UFTimeConstant = heatersTempCalc.timeUFDecayMS * UFTauCPerMS; heatersTempCalc.tempUFFill = tempLastFill + UFTimeConstant; - F32 ultrafilterPart = ( ULTRAFILTER_VOLUME_ML / targetFillVolML ) * heatersTempCalc.tempUFFill; - F32 fillPart = ( ( targetFillVolML - ULTRAFILTER_VOLUME_ML ) / targetFillVolML ) * tempAvgFill; + 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 ) * RESERVOIR_TAU_C_PER_MS ); - heatersTempCalc.tempReservoirUseActual = tempReservoirEndfillActual + ( heatersTempCalc.timeReservoirFill2SwitchMS * RESERVOIR_TAU_C_PER_MS ); + F32 tempReservoirEndfillActual = tempReservoir0Actual + ( ( heatersTempCalc.timeReservoirFillMS * 0.5 ) * RsrvrTauCPerMS ); + heatersTempCalc.tempReservoirUseActual = tempReservoirEndfillActual + ( heatersTempCalc.timeReservoirFill2SwitchMS * RsrvrTauCPerMS ); } else { @@ -612,6 +595,8 @@ 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() ) { @@ -620,15 +605,15 @@ 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.tempReservoirEndFill = tempReservoirUse - ( heatersTempCalc.timeReservoirFill2SwitchMS * RsrvrTauCPerMS ); + heatersTempCalc.tempReservoir0 = heatersTempCalc.tempReservoirEndFill - ( ( heatersTempCalc.timeReservoirFillMS * 0.5 ) * RsrvrTauCPerMS ); heatersTempCalc.timeUFDecayMS = (F32)heatersTempCalc.timeReservoirCycleMS - heatersTempCalc.timeReservoirFillMS; - UFTimeConstant = heatersTempCalc.timeUFDecayMS * ULTRAFILTER_TAU_C_PER_MS; + UFTimeConstant = heatersTempCalc.timeUFDecayMS * UFTauCPerMS; heatersTempCalc.tempUFFill = tempLastFill + UFTimeConstant; - tempTargetNumerator = heatersTempCalc.tempReservoir0 - ( ( ULTRAFILTER_VOLUME_ML / targetFillVolML ) * heatersTempCalc.tempUFFill ); - targetTempDenominator = ( ( targetFillVolML - ULTRAFILTER_VOLUME_ML ) / targetFillVolML ); + tempTargetNumerator = heatersTempCalc.tempReservoir0 - ( ( heatingConstsCalRecord.ultrafilterVolmL / targetFillVolML ) * heatersTempCalc.tempUFFill ); + targetTempDenominator = ( ( targetFillVolML - heatingConstsCalRecord.ultrafilterVolmL ) / targetFillVolML ); tempTarget = tempTargetNumerator / targetTempDenominator; } else @@ -681,8 +666,9 @@ * @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. @@ -691,26 +677,48 @@ { BOOL result = FALSE; - F32 const loadcellWeight = getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); - U32 const targetDrainVolume = getU32OverrideValue( &drainVolumeTargetMl ); + 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 ) && ( getMeasuredDGPressure( PRESSURE_SENSOR_DRAIN_PUMP_INLET ) > MIN_DRAIN_INLET_PSI_EMPTY ) ) || - ( ( 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 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 ] ) ) + { + 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; @@ -756,42 +764,21 @@ /*********************************************************************//** * @brief - * The processCalibrationData function gets the calibration data and makes - * sure it is valid by checking the calibration date. The calibration date - * should not be 0. + * The initDrainParameters function initializes the drain parameters. * @details Inputs: none - * @details Outputs: reservoirsCalRecord - * @return TRUE if the calibration record is valid, otherwise FALSE + * @details Outputs: reservoirWeightUnchangeStartTime, previousReservoirWeight + * associatedLoadCell + * @param reservoirId the ID of the reservoir that it drain parameters have + * to be initialized + * @return none *************************************************************************/ -static BOOL processCalibrationData( void ) +void initDrainParameters( DG_RESERVOIR_ID_T reservoirId ) { - BOOL status = TRUE; - U32 reservoir; + // 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; - // Get the calibration record from NVDataMgmt - DG_RESERVOIR_VOLUME_RECORD_T calData = getDGReservoirsVolumeRecord(); - - for ( reservoir = 0; reservoir < NUM_OF_CAL_DATA_RSRVRS; reservoir++ ) - { -#ifndef SKIP_CAL_CHECK - // Check if the calibration data that was received from NVDataMgmt is legitimate - // The calibration date item should not be zero. If the calibration date is 0, - // then the data is not stored in the NV memory or it was corrupted. - if ( 0 == calData.reservoir[ reservoir ].calibrationTime ) - { - SET_ALARM_WITH_1_U32_DATA( ALARM_ID_DG_RESERVOIRS_INVALID_CAL_RECORD, (U32)reservoir ); - status = FALSE; - } -#endif - - // The calibration data was valid, update the local copy - reservoirsCalRecord.reservoir[ reservoir ].maxResidualFluid = calData.reservoir[ reservoir ].maxResidualFluid; - reservoirsCalRecord.reservoir[ reservoir ].normalFillVolume = calData.reservoir[ reservoir ].normalFillVolume; - reservoirsCalRecord.reservoir[ reservoir ].rsrvrUnfilledWeight = calData.reservoir[ reservoir ].rsrvrUnfilledWeight; - reservoirsCalRecord.reservoir[ reservoir ].rsrvrVolume = calData.reservoir[ reservoir ].rsrvrVolume; - } - - return status; }