Index: firmware/App/Services/Reservoirs.c =================================================================== diff -u -r2b47dd2e7974618d8899527cdbff80fa93ebc9fa -rac029c36127b916d68c0039a470c3e4c68879adf --- firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision 2b47dd2e7974618d8899527cdbff80fa93ebc9fa) +++ firmware/App/Services/Reservoirs.c (.../Reservoirs.c) (revision ac029c36127b916d68c0039a470c3e4c68879adf) @@ -1,14 +1,14 @@ /************************************************************************** * -* Copyright (c) 2020-2023 Diality Inc. - All Rights Reserved. +* Copyright (c) 2020-2024 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 * * @author (last) Dara Navaei -* @date (last) 07-Mar-2023 +* @date (last) 02-Mar-2024 * * @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,18 @@ 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. +static BOOL transferInProgress; ///< Flag indicates if transfer is in progress between reservoirs. +// ********** private function prototypes ********** + +static void publishReservoirData( void ); + /*********************************************************************//** * @brief * The initReservoirs function initializes the Reservoirs module. @@ -135,6 +150,7 @@ dataPublishCounter = DATA_PUBLISH_COUNTER_START_COUNT; nvOps.hasDisStatusBeenWrittenToNV = FALSE; nvOps.hasROGenVolBeenWrittenToNV = FALSE; + transferInProgress = FALSE; memset( &reservoirPreviousStatus, 0.0, sizeof( RESERVOIRS_PREVIOUS_STATUS ) * NUM_OF_DG_RESERVOIRS ); } @@ -158,6 +174,9 @@ 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 ), + NUM_OF_ACID_TYPE, ALARM_ID_DG_FILL_CONDUCTIVITIES_INVALID_CAL_RECORD ); } // If the mode is fault or it is standby and the RO volume has not been written already, write it @@ -181,27 +200,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 +226,9 @@ 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 ), + NUM_OF_ACID_TYPE, ALARM_ID_DG_FILL_CONDUCTIVITIES_INVALID_CAL_RECORD ); + if ( TRUE == calStatus ) { result = SELF_TEST_STATUS_PASSED; @@ -287,6 +289,11 @@ cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } + if ( FALSE == cmdResponse.rejected ) + { + transferInProgress = FALSE; + } + sendCommandResponseMsg( &cmdResponse ); } @@ -335,6 +342,11 @@ cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } + if ( FALSE == cmdResponse.rejected ) + { + transferInProgress = TRUE; + } + sendCommandResponseMsg( &cmdResponse ); } @@ -356,16 +368,16 @@ 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() ) || - ( DG_GEN_IDLE_MODE_STATE_HANDLE_BAD_FILL == getCurrentGenIdleState() ) ) ) + 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; cmdResponse.rejected = FALSE; - if ( ( FALSE == isAlarmActive( ALARM_ID_DG_ACID_BOTTLE_LOW_VOLUME ) ) || // reject moving to fill mode if + 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 ); @@ -501,8 +513,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 +532,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; } /*********************************************************************//** @@ -540,6 +557,18 @@ /*********************************************************************//** * @brief + * The isReservoirTransferInProgress function gets the transferInProgress flag. + * @details Inputs: transferInProgress + * @details Outputs: none + * @return reservoir in progress flag. + *************************************************************************/ +BOOL isReservoirTransferInProgress( void ) +{ + return transferInProgress; +} + +/*********************************************************************//** + * @brief * The getTargetDialysateFlowLPM function returns the target dialysate flow * rate in L/min. * @details Inputs: none @@ -561,7 +590,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 +642,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 +678,9 @@ resetFillStatusParameters(); isThisTheFirstCycle = FALSE; } + + // Constantly update the trimmer heater target temperature + setHeaterTargetTemperature( DG_TRIMMER_HEATER, getTrimmerHeaterTargetTemperature() ); } /*********************************************************************//** @@ -645,7 +693,7 @@ *************************************************************************/ F32 getPrimaryHeaterTargetTemperature( void ) { - F32 tempTargetC = heatersTempCalc.tempTargetTrimmer; + F32 tempTargetC = getTrimmerHeaterTargetTemperature(); F32 priTargetTempC = 0.0F; F32 targetFillVolML = getTargetFillVolumeML(); F32 UFTimeConstant = 0.0F; @@ -676,21 +724,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 +851,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 +877,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 +898,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 +958,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 +997,7 @@ *************************************************************************/ void tareLoadCellsAtEmpty( DG_RESERVOIR_ID_T reservoirId ) { - U32 const targetDrainVolume = getU32OverrideValue( &drainVolumeTargetMl ); + U32 targetDrainVolume = getU32OverrideValue( &drainVolumeTargetMl ); if ( TRUE == tareLoadCellRequest ) { @@ -943,7 +1042,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 +1250,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 +1266,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; +} + /**@}*/