/************************************************************************** * * 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 * * @author (last) Michael Garthwaite * @date (last) 08-Aug-2022 * * @author (original) Sean * @date (original) 18-Mar-2020 * ***************************************************************************/ #include // for memcpy() #include "ConcentratePumps.h" #include "DrainPump.h" #include "Heaters.h" #include "LoadCell.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" #include "Timers.h" #include "Valves.h" /** * @addtogroup Reservoirs * @{ */ // ********** private definitions ********** #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 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. #define NUM_OF_ACID_AND_BICARB_NV_DATA_TO_CHECK 1 ///< Number of acid and bicarb non-volatile data to check. // ********** 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. F32 tempRsrvr0ActualTrimmer; ///< Temperature actual reservoir in C. F32 tempFillMixAvgTrimmer; ///< Temperature fill mix average trimmer in C. F32 tempRsrvrEndFillTrimmer; ///< Temperature reservoir end fill trimmer in C. } 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 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). /// 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 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 DG_ACID_CONCENTRATES_RECORD_T acidConcentrateCalRecord; ///< Acid concentrate calibration record. static DG_BICARB_CONCENTRATES_RECORD_T bicarbConcentrateCalRecord; ///< Bicarb concentrate 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 BOOL hasROVolBeenWrittenToNV; ///< Boolean flag to indicate whether the RO volume has been written to NV RAM or not. static BOOL hasDisinfectStatusBeenSet; ///< Boolean flag to indicate whether disinfect has been voided after starting treatment. /*********************************************************************//** * @brief * The initReservoirs function initializes the Reservoirs module. * @details Inputs: none * @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; targetFillFlowRateLPM = 0.0; isThisTheFirstCycle = TRUE; dataPublishCounter = DATA_PUBLISH_COUNTER_START_COUNT; hasROVolBeenWrittenToNV = FALSE; hasDisinfectStatusBeenSet = FALSE; 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, 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 ); } if ( ( ( DG_MODE_FAUL == getCurrentOperationMode() ) || ( DG_MODE_STAN == getCurrentOperationMode() ) ) && ( FALSE == hasROVolBeenWrittenToNV ) ) { BOOL status; DG_OP_MODE_T prevMode = getPreviousOperationMode(); switch( prevMode ) { case DG_MODE_GENE: case DG_MODE_FILL: case DG_MODE_DRAI: case DG_MODE_FLUS: case DG_MODE_HEAT: case DG_MODE_CHEM: // Mode is changing, write the RO generated volume to the RTC RAM and set the service flag to FALSE // since this is not a from a service change status = setROWaterGeneratedL( getROGeneratedVolumeL() ); break; } if ( TRUE == status ) { hasROVolBeenWrittenToNV = TRUE; } } if ( ( FALSE == hasDisinfectStatusBeenSet ) && ( MODE_TREA == hdModes.hdMode ) ) { BOOL status = setDisinfectStatus( FALSE ); if ( TRUE == status ) { hasDisinfectStatusBeenSet = TRUE; } } // 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; } } /*********************************************************************//** * @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 ); // Get the calibration values of acid and bicarb calStatus |= getNVRecord2Driver( GET_CAL_ACID_CONCENTREATES, (U08*)&acidConcentrateCalRecord, sizeof( acidConcentrateCalRecord ), NUM_OF_ACID_AND_BICARB_NV_DATA_TO_CHECK, ALARM_ID_DG_ACID_CONCENTRATE_INVALID_CAL_RECORD ); 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 ); if ( TRUE == calStatus ) { result = SELF_TEST_STATUS_PASSED; } else { result = SELF_TEST_STATUS_FAILED; } return result; } /*********************************************************************//** * @brief * The setActiveReservoirCmd function sets the given reservoir as active * (meaning HD will be drawing from this reservoir). * @details Inputs: none * @details Outputs: Specified reservoir is set as active. * @param resID ID of reservoir to set as active * @return none *************************************************************************/ void setActiveReservoirCmd( DG_RESERVOIR_ID_T resID ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_SWITCH_RESERVOIR; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // 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 ); setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRI, VALVE_STATE_R1_C_TO_NO ); break; case DG_RESERVOIR_2: activeReservoir.data = (U32)resID; cmdResponse.rejected = FALSE; setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); setValveState( VRO, VALVE_STATE_R2_C_TO_NC ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); break; default: // invalid reservoir given - cmd will be NAK'd w/ false result. cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_PARAMETER; break; } } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The changeValveSettingCmd function changes valve settings according to * the given setting ID. * @details Inputs: none * @details Outputs: Specified valve settings has been setup. * @param valveSettingID ID of valve setting to change valves to * @return none *************************************************************************/ void changeValveSettingCmd( DG_VALVE_SETTING_ID_T valveSettingID ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_VALVE_SETTING; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // valve setting command only valid in generation idle mode if ( DG_MODE_GENE == getCurrentOperationMode() ) { switch ( valveSettingID ) { case DG_VALVE_SETTING_R1_TO_R2: cmdResponse.rejected = FALSE; setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); break; case DG_VALVE_SETTING_R2_TO_R1: cmdResponse.rejected = FALSE; setValveState( VRO, VALVE_STATE_R2_C_TO_NC ); setValveState( VRI, VALVE_STATE_R1_C_TO_NO ); break; default: // invalid reservoir given - cmd will be NAK'd w/ false result. cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_PARAMETER; break; } } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * 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 fillTargeteFlowLPM target fill flow rate in L/min * @return none *************************************************************************/ void startFillCmd( U32 fillToVolMl, F32 fillTargetLPM ) { DG_CMD_RESPONSE_T cmdResponse; 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() ) || ( 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 ( FALSE == isAlarmActive( ALARM_ID_DG_BICARB_BOTTLE_LOW_VOLUME ) ) ) // alarm is active { requestNewOperationMode( DG_MODE_FILL ); } } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_PARAMETER; } } else { 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 generation idle mode * @return none *************************************************************************/ void stopFillCmd( void ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_STOP_FILL; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // regardless of mode, clear bad fill flag if HD is asking us to stop filling setBadAvgConductivityDetectedFlag( FALSE ); // stop fill command only valid in fill mode if ( DG_MODE_FILL == getCurrentOperationMode() ) { requestNewOperationMode( DG_MODE_GENE ); cmdResponse.rejected = FALSE; } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The startDrainCmd function handles a drain command from the HD. * @details Inputs: none * @details Outputs: Start draining in generation idle mode * @param drainCmd drain command data record * @return none *************************************************************************/ void startDrainCmd( DRAIN_CMD_T drainCmd ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_START_DRAIN; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // 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; } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_PARAMETER; } } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The stopDrainCmd function handles a stop drain command from the HD. * @details Inputs: none * @details Outputs: move to generation idle mode * @return none *************************************************************************/ void stopDrainCmd( void ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_STOP_DRAIN; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // stop drain command only valid in drain mode if ( DG_MODE_DRAI == getCurrentOperationMode() ) { drainVolumeTargetMl.data = 0; requestNewOperationMode( DG_MODE_GENE ); cmdResponse.rejected = FALSE; } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The setHDOperationMode function sets HD operation mode value. * @details Inputs: none * @details Outputs: hdMode * @param mode which is HD mode * @param subMode which is HD submode * @return none *************************************************************************/ void setHDOperationMode( U32 mode, U32 subMode ) { hdModes.hdMode = (HD_OP_MODE_T)mode; hdModes.hdSubMode = subMode; } /*********************************************************************//** * @brief * The getHDOperationMode function copies the provided buffer with the HD * mode and submode. * @details Inputs: none * @details Outputs: hdMode * @param mode* pointer to the buffer of type HD_MODE_SUB_MODE_T * @return none *************************************************************************/ void getHDOperationMode( HD_MODE_SUB_MODE_T* mode ) { memcpy( mode, &hdModes, sizeof( HD_MODE_SUB_MODE_T ) ); } /*********************************************************************//** * @brief * 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 getTrimmerHeaterTargetTemperature function returns the trimmer heater * target temperature in C. * @details Inputs: none * @details Outputs: heatersTempCalc * @return trimmer target temperature in C *************************************************************************/ F32 getTrimmerHeaterTargetTemperature( void ) { return heatersTempCalc.tempTargetTrimmer; } /*********************************************************************//** * @brief * The getReservoirWeight function returns the small filtered weight * of the reservoir's associated load cell. * @details Inputs: none * @details Outputs: none * @param reservoirId id of reservoir to get weight from * @return small filtered weight *************************************************************************/ F32 getReservoirWeight( DG_RESERVOIR_ID_T reservoirId ) { return getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); } /*********************************************************************//** * @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; // Check if this is the first time that the dialysate heating parameters are set in DG if ( TRUE == isThisTheFirstCycle ) { resetFillStatusParameters(); isThisTheFirstCycle = FALSE; } } /*********************************************************************//** * @brief * The getPrimaryHeaterTargetTemperature function calculates the primary * heater target temperature and returns target temperature value. * @details Inputs: heatingConstsCalRecord * @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 priTargetTemp = 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 ); F32 targetROFlowLPM = getTargetROPumpFlowRateLPM(); F32 tgtAicdFlowLPM = getConcentratePumpTargetFlowMLPM( CONCENTRATEPUMPS_CP1_ACID ) / ML_PER_LITER; F32 tgtBicarbFlowLPM = getConcentratePumpTargetFlowMLPM( CONCENTRATEPUMPS_CP2_BICARB ) / ML_PER_LITER; F32 tgtTotalFlowLPM = targetROFlowLPM + tgtAicdFlowLPM + tgtBicarbFlowLPM; 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 * HALF ) * 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; } if ( targetROFlowLPM > 0 ) { F32 acidTemperature = acidConcentrateCalRecord.acidConcentrate[ CAL_DATA_ACID_CONCENTRATE_1 ].acidBottleTemperature; F32 bicarbTemperature = bicarbConcentrateCalRecord.bicarbConcentrate[ CAL_DATA_BICARB_CONCENTRATE_1 ].bicarbBottleTemperature; priTargetTemp = ( tempTarget * ( tgtTotalFlowLPM / targetROFlowLPM ) ) - ( acidTemperature * ( tgtAicdFlowLPM / targetROFlowLPM ) ) - ( bicarbTemperature * ( tgtBicarbFlowLPM / targetROFlowLPM ) ); } return priTargetTemp; } /*********************************************************************//** * @brief * The getReservoirCurrentTemperature function calculates the reservoir's * current temperature and returns target temperature value. * @details Inputs: heatingConstsCalRecord * @details Outputs: heatersTempCalc * @return primary heater target temperature *************************************************************************/ F32 getReservoirCurrentTemperature( void ) { F32 tempRsrvrActual = 0.0; F32 fillROAvgActual = getAvgFillTemperature(); F32 targetFillVolML = getTargetFillVolumeML(); F32 UFTauCPerMS = heatingConstsCalRecord.ultrafilterTempTauCPerMin / ( SEC_PER_MIN * MS_PER_SECOND ); F32 tempLastFill = getLastFillTemperature(); F32 tempUFFill = tempLastFill + ( heatersTempCalc.timeUFDecayMS * UFTauCPerMS ); F32 rsrvrTauCPerMS = heatingConstsCalRecord.reservoirTempTauCPerMin / ( SEC_PER_MIN * MS_PER_SECOND ); F32 targetROFlowLPM = getTargetROPumpFlowRateLPM(); F32 tgtAicdFlowLPM = getConcentratePumpTargetFlowMLPM( CONCENTRATEPUMPS_CP1_ACID ) / ML_PER_LITER; F32 tgtBicarbFlowLPM = getConcentratePumpTargetFlowMLPM( CONCENTRATEPUMPS_CP2_BICARB ) / ML_PER_LITER; F32 tgtTotalFlowLPM = targetROFlowLPM + tgtAicdFlowLPM + tgtBicarbFlowLPM; if ( tgtTotalFlowLPM > 0 ) { F32 acidTemperature = acidConcentrateCalRecord.acidConcentrate[ CAL_DATA_ACID_CONCENTRATE_1 ].acidBottleTemperature; F32 bicarbTemperature = bicarbConcentrateCalRecord.bicarbConcentrate[ CAL_DATA_BICARB_CONCENTRATE_1 ].bicarbBottleTemperature; heatersTempCalc.tempFillMixAvgTrimmer = ( fillROAvgActual * ( tgtTotalFlowLPM / targetROFlowLPM ) ) + ( acidTemperature * ( tgtAicdFlowLPM / targetROFlowLPM ) ) + ( bicarbTemperature * ( tgtBicarbFlowLPM / targetROFlowLPM ) ); heatersTempCalc.tempRsrvr0ActualTrimmer = ( ( heatingConstsCalRecord.ultrafilterVolmL / targetFillVolML ) * tempUFFill ) + ( ( ( targetFillVolML - heatingConstsCalRecord.ultrafilterVolmL ) / targetFillVolML ) * heatersTempCalc.tempFillMixAvgTrimmer ); heatersTempCalc.tempRsrvrEndFillTrimmer = heatersTempCalc.tempRsrvr0ActualTrimmer + ( ( heatersTempCalc.timeReservoirFillMS * HALF ) * rsrvrTauCPerMS ); tempRsrvrActual = heatersTempCalc.tempRsrvrEndFillTrimmer + ( ( heatersTempCalc.timeReservoirFillMS * HALF ) * rsrvrTauCPerMS ); } return tempRsrvrActual; } /*********************************************************************//** * @brief * The getReservoirsCalRecord function returns the reservoirs' calibration * record. * @details Inputs: reservoirsCalRecord * @details Outputs: none * @return reservoirs' calibration record *************************************************************************/ DG_RESERVOIR_VOLUME_RECORD_T getReservoirsCalRecord( void ) { return reservoirsCalRecord; } /*********************************************************************//** * @brief * The getAcidConcentrateCalRecord function fills the provided buffer with * the acid concentrate record. * @details Inputs: acidConcentrateCalRecord * @details Outputs: none * @param acidRecord which is the pointer to the provided buffer * @return none *************************************************************************/ void getAcidConcentrateCalRecord( DG_ACID_CONCENTRATES_RECORD_T* acidRecord ) { memcpy( acidRecord, &acidConcentrateCalRecord, sizeof( DG_ACID_CONCENTRATES_RECORD_T ) ); } /*********************************************************************//** * @brief * The getBicarbConcentrateCalRecord function fills the provided buffer with * the bicarb concentrate record. * @details Inputs: bicarbConcentrateCalRecord * @details Outputs: none * @param bicarbRecord which is the pointer to the provided buffer * @return none *************************************************************************/ void getBicarbConcentrateCalRecord( DG_BICARB_CONCENTRATES_RECORD_T* bicarbRecord ) { memcpy( bicarbRecord, &bicarbConcentrateCalRecord, sizeof( DG_BICARB_CONCENTRATES_RECORD_T ) ); } /*********************************************************************//** * @brief * The hasTargetFillVolumeReached function checks if the target fill volume * for specific reservoir has been reached. * @details Inputs: fillVolumeTargetMl * @details Outputs: none * @param reservoirId reservoir id * @return TRUE if target fill volume has been reached, FALSE if not. *************************************************************************/ BOOL hasTargetFillVolumeBeenReached( DG_RESERVOIR_ID_T reservoirId ) { 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: 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. *************************************************************************/ BOOL hasTargetDrainVolumeBeenReached( DG_RESERVOIR_ID_T reservoirId, U32 timeout ) { BOOL result = FALSE; F32 loadcellWeightML = getLoadCellSmallFilteredWeight( associatedLoadCell[ reservoirId ] ); U32 drainPumpFeedbackRPM = getDrainPumpMeasuredRPM(); // 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 ) { // 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; // Calculate the flow reservoirPreviousStatus[ reservoirId ].previousDrainFlowML = reservoirPreviousStatus[ reservoirId ].previousReservoirWeightG - loadcellWeightML; // 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; } /*********************************************************************//** * @brief * The tareLoadCellsAtEmpty function tares the load cells for the given * reservoir when empty and tare request is pending. * @details Inputs: tareLoadCellRequest * @details Outputs: tareLoadCellRequest * @param reservoirId ID of reservoir to tare * @return none *************************************************************************/ void tareLoadCellsAtEmpty( DG_RESERVOIR_ID_T reservoirId ) { U32 const targetDrainVolume = getU32OverrideValue( &drainVolumeTargetMl ); if ( TRUE == tareLoadCellRequest ) { tareLoadCellRequest = FALSE; if ( MIN_RESERVOIR_VOLUME_ML == targetDrainVolume ) { tareLoadCell( associatedLoadCell[ reservoirId ] ); tareLoadCell( redundantLoadCell[ reservoirId ] ); } } } /*********************************************************************//** * @brief * The isReservoirTarePending function determines whether a reservoir tare * request is currently pending. * @details Inputs: tareLoadCellRequest * @details Outputs: none * @return tareLoadCellRequest *************************************************************************/ BOOL isReservoirTarePending( void ) { return tareLoadCellRequest; } /*********************************************************************//** * @brief * 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 *************************************************************************/ void initDrainParameters( DG_RESERVOIR_ID_T reservoirId ) { // 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; } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testSetDGActiveReservoirOverride function overrides the active reservoir. * @details Inputs: activeReservoir * @details Outputs: activeReservoir * @param value override active reservoir ID * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetDGActiveReservoirOverride( DG_RESERVOIR_ID_T value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; activeReservoir.ovData = value; activeReservoir.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The activeReservoir function resets the override of the active reservoir. * @details Inputs: activeReservoir * @details Outputs: activeReservoir * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetDGActiveReservoirOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; activeReservoir.override = OVERRIDE_RESET; activeReservoir.ovData = activeReservoir.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetReservoirFillVolumeMlOverride function overrides the target * reservoir fill volume (in mL). * @details Inputs: fillVolumeTargetMl * @details Outputs: fillVolumeTargetMl * @param value override target reservoir fill volume (in mL) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetReservoirFillVolumeMlOverride( U32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; fillVolumeTargetMl.ovData = value; fillVolumeTargetMl.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetReservoirFillVolumeMlOverride function resets the override of * the target reservoir fill volume. * @details Inputs: fillVolumeTargetMl * @details Outputs: fillVolumeTargetMl * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetReservoirFillVolumeMlOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; fillVolumeTargetMl.override = OVERRIDE_RESET; fillVolumeTargetMl.ovData = fillVolumeTargetMl.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetReservoirDrainVolumeMlOverride function overrides the target * reservoir drain volume (in mL). * @details Inputs: drainVolumeTargetMl * @details Outputs: drainVolumeTargetMl * @param value override target reservoir drain volume (in mL) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetReservoirDrainVolumeMlOverride( U32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; drainVolumeTargetMl.ovData = value; drainVolumeTargetMl.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetReservoirDrainVolumeMlOverride function resets the override of * the target reservoir drain volume. * @details Inputs: drainVolumeTargetMl * @details Outputs: drainVolumeTargetMl * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetReservoirDrainVolumeMlOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; drainVolumeTargetMl.override = OVERRIDE_RESET; drainVolumeTargetMl.ovData = drainVolumeTargetMl.ovInitData; } 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; } /**@}*/