Index: firmware/App/Services/Reservoirs.c =================================================================== diff -u -re7e9ea67b63911dc9d8d46166fdd09e102111e3f -rf847ac4a7a0a080c4b62886243f5b863c4e7730d --- firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision e7e9ea67b63911dc9d8d46166fdd09e102111e3f) +++ firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision f847ac4a7a0a080c4b62886243f5b863c4e7730d) @@ -7,8 +7,8 @@ * * @file Reservoirs.c * -* @author (last) Dara Navaei -* @date (last) 07-Mar-2023 +* @author (last) James Walter Taylor +* @date (last) 01-Sep-2023 * * @author (original) Sean * @date (original) 18-Mar-2020 @@ -55,6 +55,14 @@ #define DATA_PUBLISH_COUNTER_START_COUNT 5 ///< Data publish counter start count. #define NUM_OF_ACID_AND_BICARB_NV_DATA_TO_CHECK 1 ///< Number of acid and bicarb non-volatile data to check. +// The gain and offset are for the equation to account for the loss of temperature during the transition from DG to HD and back +/* + * Qd = 100 mL/min Tloss = 1.5C + * Qd = 500 mL/min Tloss = 0.5C + */ +#define TRIMMER_HEATER_TARGET_TEMP_GAIN -0.002F ///< Trimmer heater target temperature gain. +#define TRIMMER_HEATER_TARGET_TEMP_OFFSET 1.7F ///< Trimmer heater target temperature offset. + // ********** private data ********** /// Heaters temperature calculation data structure @@ -93,10 +101,11 @@ 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 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 serves the HD dialysate need. +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 reservoirDataPublishInterval = { RESERVOIR_DATA_PUB_INTERVAL, + RESERVOIR_DATA_PUB_INTERVAL, 0, 0 }; ///< Interval (in ms) at which to publish reservoir data to CAN bus. /// 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 }; @@ -110,12 +119,17 @@ static DG_HEATING_CAL_RECORD_T heatingConstsCalRecord; ///< DG heating calibration record. static DG_ACID_CONCENTRATES_RECORD_T acidConcentrateCalRecord; ///< Acid concentrate calibration record. static DG_BICARB_CONCENTRATES_RECORD_T bicarbConcentrateCalRecord; ///< Bicarb concentrate calibration record. +static DG_CHEMICALS_FILL_COND_CAL_RECORD_T fillCondCalRecord; ///< Fill conductivities calibration record. static F32 targetFillFlowRateLPM; ///< Target fill flow rate in L/min. static BOOL isThisTheFirstCycle; ///< Boolean flag to indicate whether this is the first cycle. static RESERVOIRS_PREVIOUS_STATUS reservoirPreviousStatus[ NUM_OF_DG_RESERVOIRS ]; ///< Reservoirs previous status. static HD_MODE_SUB_MODE_T hdModes; ///< HD operations mode. static NV_OPS_T nvOps; ///< Non-volatile memory operations. +// ********** private function prototypes ********** + +static void publishReservoirData( void ); + /*********************************************************************//** * @brief * The initReservoirs function initializes the Reservoirs module. @@ -158,6 +172,8 @@ getNVRecord2Driver( GET_CAL_HEATING_RECORD, (U08*)&heatingConstsCalRecord, sizeof( heatingConstsCalRecord ), NUM_OF_CAL_DATA_RSRVRS, ALARM_ID_DG_HEATING_INVALID_CAL_RECORD ); + + getNVRecord2Driver( GET_CAL_FILL_CONDUCTIVITIES_RECORD, (U08*)&fillCondCalRecord, sizeof( fillCondCalRecord ), 0, ALARM_ID_NO_ALARM ); } // If the mode is fault or it is standby and the RO volume has not been written already, write it @@ -181,27 +197,7 @@ } } - // publish active reservoir, fill/drain volume targets at 1 Hz. - 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 = getReservoirCurrentTemperature(); - 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; - } + publishReservoirData(); } /*********************************************************************//** @@ -227,6 +223,8 @@ calStatus |= getNVRecord2Driver( GET_CAL_BICARB_CONCENTRATES, (U08*)&bicarbConcentrateCalRecord, sizeof( bicarbConcentrateCalRecord ), NUM_OF_ACID_AND_BICARB_NV_DATA_TO_CHECK, ALARM_ID_DG_BICARB_CONCENTRATE_INVALID_CAL_RECORD ); + calStatus |= getNVRecord2Driver( GET_CAL_FILL_CONDUCTIVITIES_RECORD, (U08*)&fillCondCalRecord, sizeof( fillCondCalRecord ), 0, ALARM_ID_NO_ALARM ); + if ( TRUE == calStatus ) { result = SELF_TEST_STATUS_PASSED; @@ -501,8 +499,12 @@ *************************************************************************/ void setHDOperationMode( U32 mode, U32 subMode ) { + // thread protection for queue operations + _disable_IRQ(); hdModes.hdMode = (HD_OP_MODE_T)mode; hdModes.hdSubMode = subMode; + // release thread protection + _enable_IRQ(); } /*********************************************************************//** @@ -516,7 +518,8 @@ *************************************************************************/ void getHDOperationMode( HD_MODE_SUB_MODE_T* mode ) { - memcpy( mode, &hdModes, sizeof( HD_MODE_SUB_MODE_T ) ); + mode->hdMode = hdModes.hdMode; + mode->hdSubMode = hdModes.hdSubMode; } /*********************************************************************//** @@ -561,7 +564,8 @@ *************************************************************************/ F32 getTrimmerHeaterTargetTemperature( void ) { - return heatersTempCalc.tempTargetTrimmer; + return heatersTempCalc.tempTargetTrimmer + ( getTargetDialysateFlowLPM() * TRIMMER_HEATER_TARGET_TEMP_GAIN * ML_PER_LITER ) + + TRIMMER_HEATER_TARGET_TEMP_OFFSET; } /*********************************************************************//** @@ -612,6 +616,21 @@ /*********************************************************************//** * @brief + * The getTargetDrainVolumeML function returns the target drain volume + * in milliliters. + * @details Inputs: drainVolumeTargetMl + * @details Outputs: none + * @return target drain volume in milliliters + *************************************************************************/ +U32 getTargetDrainVolumeML( void ) +{ + U32 targetDrainVolML = getU32OverrideValue( &drainVolumeTargetMl ); + + return targetDrainVolML; +} + +/*********************************************************************//** + * @brief * The setDialysateHeatingParameters function sets the dialysate heating * parameters. * @details Inputs: none @@ -633,6 +652,9 @@ resetFillStatusParameters(); isThisTheFirstCycle = FALSE; } + + // Constantly update the trimmer heater target temperature + setHeaterTargetTemperature( DG_TRIMMER_HEATER, getTrimmerHeaterTargetTemperature() ); } /*********************************************************************//** @@ -645,7 +667,7 @@ *************************************************************************/ F32 getPrimaryHeaterTargetTemperature( void ) { - F32 tempTargetC = heatersTempCalc.tempTargetTrimmer; + F32 tempTargetC = getTrimmerHeaterTargetTemperature(); F32 priTargetTempC = 0.0F; F32 targetFillVolML = getTargetFillVolumeML(); F32 UFTimeConstant = 0.0F; @@ -676,21 +698,21 @@ * 8. T_primary_target = T_mix_target * (Q_total / Q_RO) - (Q_acid / Q_RO) * T_acid - (Q_bicarb / Q_RO) * T_bicarb */ - tempReservoirUse = heatersTempCalc.tempTargetTrimmer; + tempReservoirUse = getTrimmerHeaterTargetTemperature(); heatersTempCalc.tempReservoirEndFill = tempReservoirUse - ( heatersTempCalc.timeReservoirFill2SwitchMS * RsrvrTauCPerMS ); heatersTempCalc.tempReservoir0 = heatersTempCalc.tempReservoirEndFill - ( ( heatersTempCalc.timeReservoirFillMS * HALF ) * RsrvrTauCPerMS ); heatersTempCalc.timeUFDecayMS = (F32)heatersTempCalc.timeReservoirCycleMS - heatersTempCalc.timeReservoirFillMS; UFTimeConstant = heatersTempCalc.timeUFDecayMS * UFTauCPerMS; heatersTempCalc.tempUFFill = tempLastFill + UFTimeConstant; - + // TODO If tempUFFill < 25 make it 25 C or apply exponential Tau tempTargetNumerator = heatersTempCalc.tempReservoir0 - ( ( heatingConstsCalRecord.ultrafilterVolmL / targetFillVolML ) * heatersTempCalc.tempUFFill ); tempTargetDenominator = ( ( targetFillVolML - heatingConstsCalRecord.ultrafilterVolmL ) / targetFillVolML ); tempTargetC = tempTargetNumerator / tempTargetDenominator; } else { - tempTargetC = heatersTempCalc.tempTargetTrimmer + RESERVOIR_EXTRA_TEMPERATURE; + tempTargetC = getTrimmerHeaterTargetTemperature() + RESERVOIR_EXTRA_TEMPERATURE; } } @@ -803,6 +825,20 @@ /*********************************************************************//** * @brief + * The getFillChemicalCondRecord function fills the provided buffer with + * the fill conductivities record. + * @details Inputs: fillCondCalRecord + * @details Outputs: none + * @param fillRecord which is the pointer to the provided buffer + * @return none + *************************************************************************/ +void getFillChemicalCondRecord( DG_CHEMICALS_FILL_COND_CAL_RECORD_T* fillRecord ) +{ + memcpy( fillRecord, &fillCondCalRecord, sizeof( DG_CHEMICALS_FILL_COND_CAL_RECORD_T ) ); +} + +/*********************************************************************//** + * @brief * The hasTargetFillVolumeReached function checks if the target fill volume * for specific reservoir has been reached. * @details Inputs: fillVolumeTargetMl @@ -815,8 +851,16 @@ 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 ); + BOOL hasTargetReached = FALSE; + if ( DG_HANDLE_BAD_FILL_STATE_FLUSH_FILL == getCurrentGenIdleBadFillState() ) + { + // In bad fill flush fill we fill to 1000 mL + targetFillVolume = BAD_FLUSH_FILL_TARGET_VOLUME_ML; + } + + 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 ) { @@ -828,20 +872,19 @@ /*********************************************************************//** * @brief - * The hasTargetDrainVolumeReached function checks if the target drain volume - * for specific reservoir has been reached or exceed time limit. - * @details Inputs: reservoirWeightUnchangeStartTime, reservoirWeightUnchangeStartTime - * @details Outputs: reservoirWeightUnchangeStartTime, tareLoadCellRequest, - * reservoirWeightUnchangeStartTime + * The hasTargetDrainToZeroBeenReached function checks if the specific + * reservoir has been drained to zero or it has exceeded the time limit. + * @details Inputs: reservoirWeightUnchangeStartTime, associatedLoadCell + * @details Outputs: reservoirWeightUnchangeStartTime, tareLoadCellRequest * @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. *************************************************************************/ -BOOL hasTargetDrainVolumeBeenReached( DG_RESERVOIR_ID_T reservoirId, U32 timeout ) +BOOL hasTargetDrainToZeroBeenReached( DG_RESERVOIR_ID_T reservoirId, U32 timeout ) { BOOL result = FALSE; F32 loadcellWeightML = getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); - U32 drainPumpFeedbackRPM = getDrainPumpMeasuredRPM(); + U32 drainPumpFeedbackRPM = getDrainPumpMeasuredRPM( DRAIN_PUMP_HALL_SNSR_FB ); // 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 @@ -889,6 +932,36 @@ /*********************************************************************//** * @brief + * The hasTargetDrainVolumeReached function checks if the target drain volume + * for specific reservoir has been reached or exceeded time limit. + * @details Inputs: reservoirWeightUnchangeStartTime, associatedLoadCell + * @details Outputs: reservoirWeightUnchangeStartTime + * @param reservoirId reservoir id + * @param targetVolumeML target volume to be drained to in milliliters + * @param timeoutMS timeout period when weight remains the same in milliseconds + * @return TRUE if target drain volume has been reached or exceeds time + * limit, FALSE if not. + *************************************************************************/ +BOOL hasTargetDrainToVolumeBeenReached( DG_RESERVOIR_ID_T reservoirId, U32 targetVolumeML, U32 timeoutMS ) +{ + // NOTE: this function is used for testing only. No timeout is implemented right now. + BOOL result = FALSE; + F32 loadcellWeightML = getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); + U32 drainPumpFeedbackRPM = getDrainPumpMeasuredRPM( DRAIN_PUMP_HALL_SNSR_FB ); + + if ( drainPumpFeedbackRPM > 0 ) + { + if ( loadcellWeightML < (F32)targetVolumeML ) + { + result = TRUE; + } + } + + return result; +} + +/*********************************************************************//** + * @brief * The tareLoadCellsAtEmpty function tares the load cells for the given * reservoir when empty and tare request is pending. * @details Inputs: tareLoadCellRequest @@ -898,7 +971,7 @@ *************************************************************************/ void tareLoadCellsAtEmpty( DG_RESERVOIR_ID_T reservoirId ) { - U32 const targetDrainVolume = getU32OverrideValue( &drainVolumeTargetMl ); + U32 targetDrainVolume = getU32OverrideValue( &drainVolumeTargetMl ); if ( TRUE == tareLoadCellRequest ) { @@ -943,7 +1016,40 @@ reservoirPreviousStatus[ reservoirId ].previousDrainFlowML = 0.0F; } +/*********************************************************************//** + * @brief + * The publishReservoirData function publishes reservoir data + * at the set interval. + * @details Inputs: dataPublishCounter, reservoirDataPublishInterval + * @details Outputs: data + * @return none + *************************************************************************/ +static void publishReservoirData( void ) +{ + // publish active reservoir, fill/drain volume targets at 1 Hz. + if ( ++dataPublishCounter >= getU32OverrideValue( &reservoirDataPublishInterval ) ) + { + 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 = getReservoirCurrentTemperature(); + 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; + } +} + + /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ @@ -1118,7 +1224,7 @@ * @details Outputs: acidConcentrateCalRecord, bicarbConcentrateCalRecord * @param acid which is the acid dialysate mixing ratio * @param bicarb which is the bicarb dialysate mixing ratio - * @return TRUE if tare successful, FALSE if not + * @return TRUE if set successful, FALSE if not *************************************************************************/ BOOL testSetDialysateMixingRatios( F32 acid, F32 bicarb ) { @@ -1134,4 +1240,50 @@ return result; } +/*********************************************************************//** + * @brief + * The testSetReservoirDataPublishIntervalOverride function overrides the + * reservoir data publish interval. + * @details Inputs: reservoirDataPublishInterval + * @details Outputs: reservoirDataPublishInterval + * @param value override reservoir data publish interval with (in ms) + * @return TRUE if override successful, FALSE if not + *************************************************************************/ +BOOL testSetReservoirDataPublishIntervalOverride( U32 value ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + U32 intvl = value / TASK_GENERAL_INTERVAL; + reservoirDataPublishInterval.ovData = intvl; + reservoirDataPublishInterval.override = OVERRIDE_KEY; + result = TRUE; + } + + return result; +} + +/*********************************************************************//** + * @brief + * The testResetReservoirDataPublishIntervalOverride function resets the + * override of the reservoir publish interval. + * @details Inputs: reservoirDataPublishInterval + * @details Outputs: reservoirDataPublishInterval + * @return TRUE if override reset successful, FALSE if not + *************************************************************************/ +BOOL testResetReservoirDataPublishIntervalOverride( void ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + reservoirDataPublishInterval.override = OVERRIDE_RESET; + reservoirDataPublishInterval.ovData = reservoirDataPublishInterval.ovInitData; + result = TRUE; + } + + return result; +} + /**@}*/