/************************************************************************** * * Copyright (c) 2020-2023 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 Dialysis.c * * @author (last) Darren Cox * @date (last) 23-Aug-2023 * * @author (original) Sean * @date (original) 15-Jan-2020 * ***************************************************************************/ #include // Using fabs() function #include "AirTrap.h" #include "BloodFlow.h" #include "Buttons.h" #include "Dialysis.h" #include "DialInFlow.h" #include "DialOutFlow.h" #include "ModeTreatment.h" #include "ModeTreatmentParams.h" #include "OperationModes.h" #include "PresOccl.h" #include "Reservoirs.h" #include "SystemCommMessages.h" #include "TaskGeneral.h" #include "Timers.h" #include "Valves.h" /** * @addtogroup Dialysis * @{ */ // ********** private definitions ********** #define MAX_UF_VOLUME_ACCURACY_ERROR_ML 100.0F ///< Maximum ultrafiltration volume accuracy error in mL over the entire treatment. #define MAX_UF_RATE_ACCURACY_ERROR_ML_MIN 10.0F ///< Maximum ultrafiltration rate accuracy error in mL/min over use of single reservoir during treatment. /// Saline bolus data broadcast interval (ms/task time) count. static const U32 SALINE_BOLUS_DATA_PUB_INTERVAL = ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ); #define MAX_SALINE_VOLUME_DELIVERED 800 ///< Maximum saline volume delivered for a treatment. #define SALINE_BOLUS_RATE_ML_MIN 300 ///< Fixed rate for saline bolus delivery. #define MAX_ACTIVE_LOAD_CELL_CHANGE_G 50.0F ///< Maximum delta between new and previous measured UF volume. /// Defined states for the Load Cell cycles. typedef enum Reservoir_Steady_Cycle { RESERVOIR_STEADY_CYCLE_START = 0, ///< Reservoir steady cycle load cell reading at Start RESERVOIR_STEADY_CYCLE_FINAL, ///< Reservoir steady cycle load cell reading at Final NUM_OF_RESERVOIR_STEADY_CYCLES ///< Number of Reservoir steady cycle load cell readings } RESERVOIR_STEADY_CYCLE_T; // ********** private data ********** static DIALYSIS_STATE_T currentDialysisState; ///< Current state of the dialysis sub-mode state machine. static UF_STATE_T currentUFState; ///< Current state of the ultrafiltration state machine. static SALINE_BOLUS_STATE_T currentSalineBolusState; ///< Current state of the saline bolus state machine. static F32 refUFVolume; ///< Current reference volume for ultrafiltration (Where should we be w/r/t ultrafiltration). static F32 measUFVolume; ///< Current total measured volume for ultrafiltration (Where are we w/r/t ultrafiltration). static F32 resStartVolume[ NUM_OF_DG_RESERVOIRS ]; ///< Reservoir start volume for ultrafiltration (i.e. where did we start with each reservoir). static F32 resFinalVolume[ NUM_OF_DG_RESERVOIRS ]; ///< Reservoir final volume for ultrafiltration (i.e. where did we end after switch with each reservoir). static F32 resStartRefVolume[ NUM_OF_DG_RESERVOIRS ]; ///< Starting ultrafiltration reference volume for reservoirs. static F32 resFinalRefVolume[ NUM_OF_DG_RESERVOIRS ]; ///< Final ultrafiltration reference volume for reservoirs. static F32 resCurrVolume[ NUM_OF_DG_RESERVOIRS ]; ///< Reservoir current volume. static F32 resLastVolume[ NUM_OF_DG_RESERVOIRS ]; ///< Previous reading of reservoir volume. static F32 measUFVolumeFromPriorReservoirs; ///< Current total ultrafiltration volume from previous reservoirs in current treatment. static F32 lcLastSteadyWeight[NUM_OF_RESERVOIR_STEADY_CYCLES][NUM_OF_LOAD_CELLS]; ///< Load Cell Last Steady Weight for drift test Start/Final cycle static U32 uFTimeMS; ///< Current elapsed ultrafiltration time (in ms). Used for calculating UF reference volume. static U32 lastUFTimeStamp; ///< HD timer value when we last took stock of ultrafiltration time (so we can determine how much time has elapsed since). static U32 setBloodFlowRate; ///< Currently set blood flow rate (from prescription). static U32 setDialysateFlowRate; ///< Currently set dialysate flow rate (from prescription). static F32 setUFVolumeML; ///< Currently set total ultrafiltration volume for treatment (from prescription). static F32 setUFRate; ///< Currently set ultrafiltration rate (from prescription). static U32 salineBolusBroadcastTimerCtr; ///< Saline bolus data broadcast timer counter used to schedule when to transmit data. static BOOL salineBolusStartRequested; ///< Flag indicates a saline bolus start has been requested by user. static BOOL salineBolusAbortRequested; ///< Flag indicates a salien bolus abort has been requested by user. static BOOL autoResumeUF; ///< Flag indicates UF should be auto-resumed after saline bolus completes. static F32 totalSalineVolumeDelivered_mL; ///< Volume (mL) in total of saline delivered so far (cumulative for all boluses including current one). static F32 bolusSalineVolumeDelivered_mL; ///< Volume (mL) of current bolus delivered so far (calculated from measured blood flow rate). static U32 bolusSalineLastVolumeTimeStamp; ///< Time stamp for last saline volume update. // ********** private function prototypes ********** static DIALYSIS_STATE_T handleDialysisUltrafiltrationState( void ); static DIALYSIS_STATE_T handleDialysisSalineBolusState( void ); static UF_STATE_T handleUFPausedState( DIALYSIS_STATE_T *dialysisState ); static UF_STATE_T handleUFRunningState( DIALYSIS_STATE_T *dialysisState ); static SALINE_BOLUS_STATE_T handleSalineBolusIdleState( void ); static SALINE_BOLUS_STATE_T handleSalineBolusWait4Pumps2Stop( void ); static SALINE_BOLUS_STATE_T handleSalineBolusInProgressState( DIALYSIS_STATE_T *dialysisState ); static SALINE_BOLUS_STATE_T handleSalineBolusMaxDeliveredState( DIALYSIS_STATE_T *dialysisState ); static void checkUFControl( void ); static void updateUFVolumes( void ); static void publishSalineBolusData( void ); static void checkLoadCellsStablePrimaryBackupDriftOutOfRange( DG_RESERVOIR_ID_T reservoirID, RESERVOIR_STEADY_CYCLE_T cycle ); /*********************************************************************//** * @brief * The initDialysis function initializes the Dialysis sub-mode module. * Calling this function will reset dialysis and therefore should only * be called when a new treatment is due to begin. * @details Inputs: none * @details Outputs: Dialysis sub-mode module initialized. * @return none *************************************************************************/ void initDialysis( void ) { U16 i; currentDialysisState = DIALYSIS_UF_STATE; currentUFState = UF_RUNNING_STATE; currentSalineBolusState = SALINE_BOLUS_STATE_IDLE; lastUFTimeStamp = getMSTimerCount(); uFTimeMS = 0; refUFVolume = 0.0; measUFVolume = 0.0; measUFVolumeFromPriorReservoirs = 0.0; setDialOutUFVolumes( refUFVolume, measUFVolume ); // Send reset UF volumes to dialysate outlet pump driver uFTimeMS = 0; lastUFTimeStamp = 0; setBloodFlowRate = 0; setDialysateFlowRate = 0; setUFVolumeML = 0.0; setUFRate = 0.0; resetDialOutRateOffset(); salineBolusBroadcastTimerCtr = 0; totalSalineVolumeDelivered_mL = 0.0; autoResumeUF = FALSE; for ( i = 0; i < NUM_OF_LOAD_CELLS; i++ ) { lcLastSteadyWeight[RESERVOIR_STEADY_CYCLE_START][i] = LOAD_CELL_ILLEGAL_WEIGHT_VALUE; lcLastSteadyWeight[RESERVOIR_STEADY_CYCLE_FINAL][i] = LOAD_CELL_ILLEGAL_WEIGHT_VALUE; } for ( i = 0; i < NUM_OF_DG_RESERVOIRS; i++ ) { resStartRefVolume[ i ] = 0.0F; resFinalRefVolume[ i ] = 0.0F; resCurrVolume[ i ] = 0.0F; resLastVolume[ i ] = 0.0F; } resetSalineBolus(); } /*********************************************************************//** * @brief * The resetSalineBolus function initializes the saline bolus variables * at start of treatment or after stopping a bolus (e.g. alarm). Total * saline bolus volume delivered will not be affected by this function. * @details Inputs: autoResumeUF * @details Outputs: salineBolusStartRequested, salineBolusAbortRequested, * bolusSalineVolumeDelivered_mL, currentSalineBolusState, autoResumeUF, * currentUFState, currentDialysisState * @return none *************************************************************************/ void resetSalineBolus( void ) { salineBolusStartRequested = FALSE; salineBolusAbortRequested = FALSE; bolusSalineVolumeDelivered_mL = 0.0; if ( currentSalineBolusState != SALINE_BOLUS_STATE_MAX_DELIVERED ) { currentSalineBolusState = SALINE_BOLUS_STATE_IDLE; } if ( TRUE == autoResumeUF ) { autoResumeUF = FALSE; currentUFState = UF_RUNNING_STATE; } currentDialysisState = DIALYSIS_UF_STATE; } /*********************************************************************//** * @brief * The transitionToDialysis function prepares for transition to dialysis sub-mode. * This function will reset anything required for resuming dialysis in a * treatment that has already begun. It does not reset everything as dialysis * may be stopped and resumed multiple times due to alarms or user intervention * and we don't want to start the treatment all over again. * @details Inputs: none * @details Outputs: none * @return none *************************************************************************/ void transitionToDialysis( void ) { PUMP_CONTROL_MODE_T mode = PUMP_CONTROL_MODE_CLOSED_LOOP; doorClosedRequired( TRUE, TRUE ); // Set last UF timestamp so UF ref is resumed from this time lastUFTimeStamp = getMSTimerCount(); // Send dialysate outlet pump latest UF volumes setDialOutUFVolumes( refUFVolume, measUFVolume ); resetReservoirsVariables(); // Set valves for dialysis setValvePosition( VDI, VALVE_POSITION_B_OPEN ); setValvePosition( VDO, VALVE_POSITION_B_OPEN ); setValvePosition( VBA, VALVE_POSITION_B_OPEN ); setValvePosition( VBV, VALVE_POSITION_B_OPEN ); // Restart pumps #ifndef _RELEASE_ if ( SW_CONFIG_ENABLE_VALUE == getSoftwareConfigStatus( SW_CONFIG_ENABLE_BLOOD_PUMP_OPEN_LOOP ) ) { mode = PUMP_CONTROL_MODE_OPEN_LOOP; } #endif setBloodPumpTargetFlowRate( setBloodFlowRate, MOTOR_DIR_FORWARD, mode ); #ifndef _RELEASE_ if ( SW_CONFIG_ENABLE_VALUE == getSoftwareConfigStatus( SW_CONFIG_ENABLE_DIALYSATE_INLET_PUMP_OPEN_LOOP ) ) { mode = PUMP_CONTROL_MODE_OPEN_LOOP; } #endif setDialInPumpTargetFlowRate( setDialysateFlowRate, MOTOR_DIR_FORWARD, mode ); setDialOutPumpTargetRate( setDialysateFlowRate + (S32)setUFRate, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_CLOSED_LOOP ); // Start Heparin pump as appropriate startHeparinPump(); // Tell DG to start heating dialysate cmdStartDGTrimmerHeater(); // Start auto-control of air trap valve startAirTrapControl(); // Set user alarm recovery actions allowed in this sub-mode setAlarmUserActionEnabled( ALARM_USER_ACTION_RESUME, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_RINSEBACK, TRUE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_END_TREATMENT, TRUE ); //Set substate for event setCurrentSubState( (U32)currentDialysisState ); setCurrent4thLevelState( (U32)currentUFState ); } /*********************************************************************//** * @brief * The startHeparinPump function sets the syringe pump running as appropriate * when starting/resuming dialysis sub-mode. * @details Inputs: Heparin treatment parameters, treatment time remaining * @details Outputs: Syringe pump started/stopped as appropriate * @return none *************************************************************************/ void startHeparinPump( void ) { HEPARIN_STATE_T currentHeparinState = getHeparinState(); U32 preStop = getTreatmentParameterU32( TREATMENT_PARAM_HEPARIN_PRE_STOP_TIME ); F32 minRem = (F32)getTreatmentTimeRemainingSecs() / (F32)SEC_PER_MIN; F32 bolusVol = getTreatmentParameterF32( TREATMENT_PARAM_HEPARIN_BOLUS_VOLUME ); F32 hepRate = getTreatmentParameterF32( TREATMENT_PARAM_HEPARIN_DISPENSE_RATE ); // Do not run syringe pump if no Heparin included in prescription or it was paused or if Heparin should be stopped at this stage of treatment if ( ( minRem > (F32)preStop ) && ( ( HEPARIN_STATE_STOPPED == currentHeparinState ) || ( HEPARIN_STATE_DISPENSING == currentHeparinState ) ) ) { if ( hepRate > 0.0 ) { startHeparinContinuous(); } else { setHeparinCompleted(); } } else { stopSyringePump(); } } /*********************************************************************//** * @brief * The setDialysisParams function sets the dialysis treatment parameters. * This function should be called prior to beginning dialysis treatment * and when the user changes one or more parameters during treatment. * @details Inputs: none * @details Outputs: dialysis treatment parameters are set. * @param bPFlow target blood pump flow rate (in mL/min) * @param dPFlow target dialysate inlet pump flow rate (in mL/min) * @param maxUFVol maximum ultrafiltration volume (in mL) * @param uFRate target ultrafiltration rate (in mL/min) * @return none *************************************************************************/ void setDialysisParams( U32 bPFlow, U32 dPFlow, F32 maxUFVol, F32 uFRate ) { setBloodFlowRate = bPFlow; setDialysateFlowRate = dPFlow; setUFVolumeML = maxUFVol; setUFRate = uFRate; // Make rate changes in real time if currently performing dialysis. if ( ( TREATMENT_DIALYSIS_STATE == getTreatmentState() ) && ( getDialysisState() != DIALYSIS_SALINE_BOLUS_STATE ) ) { PUMP_CONTROL_MODE_T mode = PUMP_CONTROL_MODE_CLOSED_LOOP; #ifndef _RELEASE_ if ( SW_CONFIG_ENABLE_VALUE == getSoftwareConfigStatus( SW_CONFIG_ENABLE_BLOOD_PUMP_OPEN_LOOP ) ) { mode = PUMP_CONTROL_MODE_OPEN_LOOP; } #endif setBloodPumpTargetFlowRate( setBloodFlowRate, MOTOR_DIR_FORWARD, mode ); #ifndef _RELEASE_ if ( SW_CONFIG_ENABLE_VALUE == getSoftwareConfigStatus( SW_CONFIG_ENABLE_DIALYSATE_INLET_PUMP_OPEN_LOOP ) ) { mode = PUMP_CONTROL_MODE_OPEN_LOOP; } #endif setDialInPumpTargetFlowRate( setDialysateFlowRate, MOTOR_DIR_FORWARD, mode ); setDialOutPumpTargetRate( setDialysateFlowRate + (S32)setUFRate, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_CLOSED_LOOP ); } } /*********************************************************************//** * @brief * The stopDialysis function stops dialysis. This function will be called * by Treatment Mode when an alarm occurs or the user pressed the stop button. * Dialysis may be resumed later. * @details Inputs: none * @details Outputs: Blood and dialysate pumps stopped. Heparin pump stopped. * @return none *************************************************************************/ void stopDialysis( void ) { // Stop pumps signalDialInPumpHardStop(); signalDialOutPumpHardStop(); signalBloodPumpHardStop(); stopSyringePump(); // Tell DG to stop heating dialysate cmdStopDGTrimmerHeater(); } /*********************************************************************//** * @brief * The signalStartSalineBolus function handles user request to initiate a * saline bolus. * @details Inputs: set bolus volume, current mode/sub-mode, bolus state * @details Outputs: salineBolusStartRequested, response sent * @return none *************************************************************************/ void signalStartSalineBolus( void ) { BOOL accept = FALSE; REQUEST_REJECT_REASON_CODE_T rejReason = REQUEST_REJECT_REASON_NONE; U32 salineBolusVolume = getTreatmentParameterU32( TREATMENT_PARAM_SALINE_BOLUS_VOLUME ); HD_OP_MODE_T currOpMode = getCurrentOperationMode(); TREATMENT_STATE_T currTreatSubMode = getTreatmentState(); // Must be in treatment mode, dialysis sub-mode, saline bolus in idle state in order to start a saline bolus if ( currOpMode != MODE_TREA ) { rejReason = REQUEST_REJECT_REASON_NOT_IN_TREATMENT_MODE; } else if ( currTreatSubMode != TREATMENT_DIALYSIS_STATE ) { rejReason = REQUEST_REJECT_REASON_INVALID_TREATMENT_STATE; } else if ( totalSalineVolumeDelivered_mL >= (F32)MAX_SALINE_VOLUME_DELIVERED ) { rejReason = REQUEST_REJECT_REASON_SALINE_MAX_VOLUME_REACHED; } else if ( DIALYSIS_SALINE_BOLUS_STATE == currentDialysisState ) { rejReason = REQUEST_REJECT_REASON_SALINE_BOLUS_IN_PROGRESS; } else { accept = TRUE; salineBolusStartRequested = TRUE; signalInitiatePressureStabilization( USE_NORMAL_STABILIZATION_PERIOD ); } // Send response sendSalineBolusResponse( accept, rejReason, salineBolusVolume ); } /*********************************************************************//** * @brief * The signalAbortSalineBolus function handles user request to abort a * saline bolus. * @details Inputs: set bolus volume, current mode/sub-mode, bolus state * @details Outputs: salineBolusAbortRequested, response sent * @return none *************************************************************************/ void signalAbortSalineBolus( void ) { BOOL accept = FALSE; REQUEST_REJECT_REASON_CODE_T rejReason = REQUEST_REJECT_REASON_NONE; U32 salineBolusVolume = getTreatmentParameterU32( TREATMENT_PARAM_SALINE_BOLUS_VOLUME ); HD_OP_MODE_T currOpMode = getCurrentOperationMode(); TREATMENT_STATE_T currTreatSubMode = getTreatmentState(); SALINE_BOLUS_STATE_T currSalineBolusState = getSalineBolusState(); // Must be in treatment mode, dialysis sub-mode, saline bolus in delivery state in order to abort a saline bolus if ( currOpMode != MODE_TREA ) { rejReason = REQUEST_REJECT_REASON_NOT_IN_TREATMENT_MODE; } else if ( currTreatSubMode != TREATMENT_DIALYSIS_STATE ) { rejReason = REQUEST_REJECT_REASON_INVALID_TREATMENT_STATE; } else if ( currSalineBolusState != SALINE_BOLUS_STATE_IN_PROGRESS ) { rejReason = REQUEST_REJECT_REASON_SALINE_BOLUS_NOT_IN_PROGRESS; } else { accept = TRUE; salineBolusAbortRequested = TRUE; signalInitiatePressureStabilization( USE_NORMAL_STABILIZATION_PERIOD ); } // Send response sendSalineBolusResponse( accept, rejReason, salineBolusVolume ); } /*********************************************************************//** * @brief * The getDialysisState function gets the current dialysis state (sub-mode). * @details Inputs: currentDialysisState * @details Outputs: none * @return currentDialysisState *************************************************************************/ DIALYSIS_STATE_T getDialysisState( void ) { return currentDialysisState; } /*********************************************************************//** * @brief * The getUltrafiltrationState function gets the current ultrafiltration state. * @details Inputs: currentUFState * @details Outputs: none * @return currentUFState *************************************************************************/ UF_STATE_T getUltrafiltrationState( void ) { return currentUFState; } /*********************************************************************//** * @brief * The getSalineBolusState function gets the current saline bolus state. * @details Inputs: currentSalineBolusState * @details Outputs: none * @return currentSalineBolusState *************************************************************************/ SALINE_BOLUS_STATE_T getSalineBolusState( void ) { return currentSalineBolusState; } /*********************************************************************//** * @brief * The getUltrafiltrationVolumeCollected function gets the current ultrafiltration * volume collected so far for current treatment. * @details Inputs: measUFVolume * @details Outputs: none * @return measUFVolume *************************************************************************/ F32 getUltrafiltrationVolumeCollected( void ) { return measUFVolume; } /*********************************************************************//** * @brief * The getUltrafiltrationReferenceVolume function gets the current ultrafiltration * reference volume. * @details Inputs: refUFVolume * @details Outputs: none * @return refUFVolume *************************************************************************/ F32 getUltrafiltrationReferenceVolume( void ) { return refUFVolume; } /*********************************************************************//** * @brief * The getTotalSalineBolusVolumeDelivered function gets the current total * saline volume delivered. * @details Inputs: totalSalineVolumeDelivered * @details Outputs: none * @return totalSalineVolumeDelivered *************************************************************************/ F32 getTotalSalineBolusVolumeDelivered( void ) { return totalSalineVolumeDelivered_mL; } /*********************************************************************//** * @brief * The pauseUF function pauses ultrafiltration. * @details Inputs: currentDialysisState, currentUFState * @details Outputs: currentUFState, outlet pump set point * @return TRUE if pause successful, FALSE if not *************************************************************************/ BOOL pauseUF( void ) { BOOL result = FALSE; REQUEST_REJECT_REASON_CODE_T rejectReason = REQUEST_REJECT_REASON_NONE; TREATMENT_STATE_T trtState = getTreatmentState(); HD_OP_MODE_T currMode = getCurrentOperationMode(); if ( ( MODE_TREA == currMode ) && ( TREATMENT_DIALYSIS_STATE == trtState ) && ( DIALYSIS_UF_STATE == currentDialysisState ) && ( UF_RUNNING_STATE == currentUFState ) ) { result = TRUE; sendTreatmentLogEventData( UF_PAUSE_EVENT, setUFRate, 0.0 ); // Go to UF paused state currentUFState = UF_PAUSED_STATE; sendTreatmentLogEventData( UF_START_RESUME_EVENT, UF_RUNNING_STATE, UF_PAUSED_STATE ); } else { if ( MODE_TREA != currMode ) { rejectReason = REQUEST_REJECT_REASON_NOT_IN_TREATMENT_MODE; } else if ( TREATMENT_DIALYSIS_STATE != trtState ) { rejectReason = REQUEST_REJECT_REASON_INVALID_TREATMENT_STATE; } else if ( DIALYSIS_UF_STATE != currentDialysisState ) { rejectReason = REQUEST_REJECT_REASON_SALINE_BOLUS_IN_PROGRESS; } else { rejectReason = REQUEST_REJECT_REASON_UF_NOT_IN_PROGESS; } } // Send response w/ reason code if rejected sendUFPauseResumeResponse( result, rejectReason, currentUFState ); return result; } /*********************************************************************//** * @brief * The resumeUF function resumes ultrafiltration. * @details Inputs: currentDialysisState, currentUFState * @details Outputs: currentUFState, outlet pump set point * @return TRUE if resume successful, FALSE if not *************************************************************************/ BOOL resumeUF( void ) { BOOL result = FALSE; REQUEST_REJECT_REASON_CODE_T rejectReason = REQUEST_REJECT_REASON_NONE; TREATMENT_STATE_T trtState = getTreatmentState(); HD_OP_MODE_T currMode = getCurrentOperationMode(); if ( ( MODE_TREA == currMode ) && ( TREATMENT_DIALYSIS_STATE == trtState ) && ( DIALYSIS_UF_STATE == currentDialysisState ) && ( UF_PAUSED_STATE == currentUFState ) ) { result = TRUE; sendTreatmentLogEventData( UF_START_RESUME_EVENT, 0.0, setUFRate ); // Restart UF time accumulation for reference volume calculation lastUFTimeStamp = getMSTimerCount(); // Go to UF running state currentUFState = UF_RUNNING_STATE; sendTreatmentLogEventData( UF_START_RESUME_EVENT, UF_PAUSED_STATE, UF_RUNNING_STATE ); } else { if ( MODE_TREA != currMode ) { rejectReason = REQUEST_REJECT_REASON_NOT_IN_TREATMENT_MODE; } else if ( TREATMENT_DIALYSIS_STATE != trtState ) { rejectReason = REQUEST_REJECT_REASON_INVALID_TREATMENT_STATE; } else if ( DIALYSIS_UF_STATE != currentDialysisState ) { rejectReason = REQUEST_REJECT_REASON_SALINE_BOLUS_IN_PROGRESS; } else { rejectReason = REQUEST_REJECT_REASON_UF_NOT_PAUSED; } } // Send response w/ reason code if rejected sendUFPauseResumeResponse( result, rejectReason, currentUFState ); // Send state data immediately for UI update broadcastTreatmentTimeAndState(); return result; } /*********************************************************************//** * @brief * The execDialysis function executes the Dialysis sub-mode state machine. * @details Inputs: currentDialysisState * @details Outputs: currentDialysisState * @return none *************************************************************************/ void execDialysis( void ) { DIALYSIS_STATE_T priorSubState = currentDialysisState; // Check ultrafiltration control during dialysis (even when ultrafiltration is paused). checkUFControl(); // Dialysis state machine switch ( currentDialysisState ) { case DIALYSIS_UF_STATE: currentDialysisState = handleDialysisUltrafiltrationState(); break; case DIALYSIS_SALINE_BOLUS_STATE: currentDialysisState = handleDialysisSalineBolusState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_HD_SOFTWARE_FAULT, SW_FAULT_ID_DIALYSIS_INVALID_STATE, currentDialysisState ) break; } // Calculate UF measured volume and provide to dialysate outlet pump controller (watch UF regardless of dialysis state) updateUFVolumes(); if ( priorSubState != currentDialysisState ) { setCurrentSubState( (U32)currentDialysisState ); SEND_EVENT_WITH_2_U32_DATA( HD_EVENT_SUB_STATE_CHANGE, priorSubState, currentDialysisState ); } // Publish saline bolus data at set interval (whether we are delivering one or not) publishSalineBolusData(); } /*********************************************************************//** * @brief * The handleDialysisUltrafiltrationState function handles the ultrafiltration * state of the Dialysis state machine. * @details Inputs: currentUFState * @details Outputs: currentUFState * @return next Dialysis state. *************************************************************************/ static DIALYSIS_STATE_T handleDialysisUltrafiltrationState( void ) { DIALYSIS_STATE_T result = DIALYSIS_UF_STATE; UF_STATE_T priorSubState = currentUFState; U32 preStop = getTreatmentParameterU32( TREATMENT_PARAM_HEPARIN_PRE_STOP_TIME ); F32 minRem = (F32)getTreatmentTimeRemainingSecs() / (F32)SEC_PER_MIN; // Stop Heparin delivery if we have reached Heparin pre-stop point if ( minRem <= (F32)preStop ) { stopSyringePump(); setHeparinCompleted(); } // Handle current ultrafiltration state switch ( currentUFState ) { case UF_PAUSED_STATE: currentUFState = handleUFPausedState( &result ); break; case UF_RUNNING_STATE: currentUFState = handleUFRunningState( &result ); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_HD_SOFTWARE_FAULT, SW_FAULT_ID_DIALYSIS_INVALID_UF_STATE, currentUFState ) currentUFState = UF_PAUSED_STATE; break; } if ( priorSubState != currentUFState ) { setCurrent4thLevelState( (U32)currentUFState ); SEND_EVENT_WITH_2_U32_DATA( HD_EVENT_SUB_STATE_CHANGE, priorSubState, currentUFState ); } return result; } /*********************************************************************//** * @brief * The handleDialysisSalineBolusState function handles the saline bolus * state of the Dialysis state machine. * @details Inputs: currentSalineBolusState * @details Outputs: currentSalineBolusState * @return next Dialysis state. *************************************************************************/ static DIALYSIS_STATE_T handleDialysisSalineBolusState( void ) { DIALYSIS_STATE_T result = DIALYSIS_SALINE_BOLUS_STATE; SALINE_BOLUS_STATE_T priorSubState = currentSalineBolusState; switch ( currentSalineBolusState ) { case SALINE_BOLUS_STATE_IDLE: currentSalineBolusState = handleSalineBolusIdleState(); break; case SALINE_BOLUS_STATE_WAIT_FOR_PUMPS_STOP: currentSalineBolusState = handleSalineBolusWait4Pumps2Stop(); break; case SALINE_BOLUS_STATE_IN_PROGRESS: currentSalineBolusState = handleSalineBolusInProgressState( &result ); break; case SALINE_BOLUS_STATE_MAX_DELIVERED: currentSalineBolusState = handleSalineBolusMaxDeliveredState( &result ); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_HD_SOFTWARE_FAULT, SW_FAULT_ID_DIALYSIS_INVALID_SALINE_BOLUS_STATE, currentSalineBolusState ) currentSalineBolusState = SALINE_BOLUS_STATE_MAX_DELIVERED; break; } // MAX DELIVERED transitions different and sends the event on its own. if ( priorSubState != currentSalineBolusState && currentSalineBolusState != SALINE_BOLUS_STATE_MAX_DELIVERED ) { setCurrent4thLevelState( (U32)currentSalineBolusState ); SEND_EVENT_WITH_2_U32_DATA( HD_EVENT_SUB_STATE_CHANGE, priorSubState, currentSalineBolusState ); } return result; } /*********************************************************************//** * @brief * The handleUFPausedState function handles the Paused state of the * ultrafiltration state machine. * @details Inputs: salineBolusStartRequested * @details Outputs: if ultrafiltration resumption requested, UF time is set to resume. * @param dialysisState next dialysis state * @return next ultrafiltration state. *************************************************************************/ static UF_STATE_T handleUFPausedState( DIALYSIS_STATE_T *dialysisState ) { UF_STATE_T result = UF_PAUSED_STATE; // Handle saline bolus start request from user if ( TRUE == salineBolusStartRequested ) { autoResumeUF = FALSE; // Go to saline bolus state if we can if ( SALINE_BOLUS_STATE_IDLE == currentSalineBolusState ) { *dialysisState = DIALYSIS_SALINE_BOLUS_STATE; setCurrent4thLevelState( (U32)currentSalineBolusState ); } else { salineBolusStartRequested = FALSE; } } return result; } /*********************************************************************//** * @brief * The handleUFRunningState function handles the Running state of the * ultrafiltration state machine. * @details Inputs: ms timer, lastUFTimeStamp * @details Outputs: UF timer incremented, UF volumes updated and provided to DPo * pump controller. * @param dialysisState next dialysis state * @return next ultrafiltration state. *************************************************************************/ static UF_STATE_T handleUFRunningState( DIALYSIS_STATE_T *dialysisState ) { UF_STATE_T result = UF_RUNNING_STATE; U32 newTime = getMSTimerCount(); U32 msSinceLast = calcTimeBetween( lastUFTimeStamp, newTime ); // Update UF time uFTimeMS += msSinceLast; lastUFTimeStamp = newTime; // Update UF ref volume in UF running state only refUFVolume += ( ( (F32)msSinceLast / MS_PER_SECOND ) / SEC_PER_MIN ) * setUFRate; updateReservoirUFTime(); // If we have reached target UF volume, UF is complete - set UF rate to zero for remainder of treatment if ( refUFVolume >= setUFVolumeML ) // TODO - is this something we want to do or should we just let UF continue? { setUFRate = 0.0; } // Handle saline bolus start request from user if ( TRUE == salineBolusStartRequested ) { if ( SALINE_BOLUS_STATE_IDLE == currentSalineBolusState ) { // Since we were doing UF prior to saline bolus, we want to auto-resume when done autoResumeUF = TRUE; // Go to UF paused state result = UF_PAUSED_STATE; // Go to saline bolus state *dialysisState = DIALYSIS_SALINE_BOLUS_STATE; setCurrent4thLevelState( (U32)currentSalineBolusState ); } else { salineBolusStartRequested = FALSE; } } return result; } /*********************************************************************//** * @brief * The handleSalineBolusIdleState function handles the idle state of the * saline bolus state machine. * @details Inputs: none * @details Outputs: * @return next saline bolus state *************************************************************************/ static SALINE_BOLUS_STATE_T handleSalineBolusIdleState( void ) { SALINE_BOLUS_STATE_T result = SALINE_BOLUS_STATE_IDLE; // Handle saline bolus start request from user if ( TRUE == salineBolusStartRequested ) { salineBolusStartRequested = FALSE; // Cmd all pumps to stop setBloodPumpTargetFlowRate( 0, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); setDialInPumpTargetFlowRate( 0, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); setDialOutPumpTargetRate( 0, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_OPEN_LOOP ); stopSyringePump(); // Begin saline bolus result = SALINE_BOLUS_STATE_WAIT_FOR_PUMPS_STOP; } return result; } /*********************************************************************//** * @brief * The handleSalineBolusWait4Pumps2Stop function handles the wait for pumps * to stop state of the saline bolus state machine. * @details Inputs: none * @details Outputs: * @return next saline bolus state *************************************************************************/ static SALINE_BOLUS_STATE_T handleSalineBolusWait4Pumps2Stop( void ) { SALINE_BOLUS_STATE_T result = SALINE_BOLUS_STATE_WAIT_FOR_PUMPS_STOP; if ( ( FALSE == isBloodPumpRunning() ) && ( FALSE == isDialInPumpRunning() ) && ( FALSE == isDialOutPumpRunning() ) ) { // Reset bolus data before we start bolusSalineVolumeDelivered_mL = 0.0; bolusSalineLastVolumeTimeStamp = getMSTimerCount(); // Bypass dialyzer setValvePosition( VDI, VALVE_POSITION_C_CLOSE ); setValvePosition( VDO, VALVE_POSITION_C_CLOSE ); // Switch to saline bag setValvePosition( VBA, VALVE_POSITION_C_CLOSE ); // Start blood pump at saline bolus rate setBloodPumpTargetFlowRate( SALINE_BOLUS_FLOW_RATE, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_CLOSED_LOOP ); // Start dialysate inlet pump at re-circ rate setDialInPumpTargetFlowRate( DIALYSATE_FLOW_RATE_FOR_RECIRC, MOTOR_DIR_FORWARD, PUMP_CONTROL_MODE_CLOSED_LOOP ); // Begin saline bolus result = SALINE_BOLUS_STATE_IN_PROGRESS; } return result; } /*********************************************************************//** * @brief * The handleSalineBolusInProgressState function handles the in-progress state of the * saline bolus state machine. * @details Inputs: none * @details Outputs: * @param dialysisState next dialysis state * @return next saline bolus state *************************************************************************/ static SALINE_BOLUS_STATE_T handleSalineBolusInProgressState( DIALYSIS_STATE_T *dialysisState ) { SALINE_BOLUS_STATE_T result = SALINE_BOLUS_STATE_IN_PROGRESS; BOOL errorFound = FALSE; F32 timeSinceLastVolumeUpdateMin = (F32)calcTimeSince( bolusSalineLastVolumeTimeStamp ) / (F32)( MS_PER_SECOND * SEC_PER_MIN ); F32 bolusTargetVolume = (F32)getTreatmentParameterU32( TREATMENT_PARAM_SALINE_BOLUS_VOLUME ); F32 bldFlowRate = getMeasuredBloodFlowRate(); F32 volSinceLastUpdateMl = bldFlowRate * timeSinceLastVolumeUpdateMin; F32 expVolSinceLastUpdateMl = (F32)getTargetBloodFlowRate() * timeSinceLastVolumeUpdateMin; // Update saline bolus volumes bolusSalineLastVolumeTimeStamp = getMSTimerCount(); bolusSalineVolumeDelivered_mL += volSinceLastUpdateMl; totalSalineVolumeDelivered_mL += volSinceLastUpdateMl; // Check for empty saline bag per arterial line pressure if ( TRUE == isSalineBagEmpty() ) { SET_ALARM_WITH_1_F32_DATA( ALARM_ID_HD_EMPTY_SALINE_BAG, getMeasuredArterialPressure() ); errorFound = TRUE; result = SALINE_BOLUS_STATE_IDLE; } // Determine if we have reached maximum saline delivery volume if ( ( totalSalineVolumeDelivered_mL >= (F32)MAX_SALINE_VOLUME_DELIVERED ) ) { result = SALINE_BOLUS_STATE_MAX_DELIVERED; setCurrent4thLevelState( (U32)result ); sendOperationStatusEvent(); } else { // Determine if bolus is complete if ( bolusSalineVolumeDelivered_mL >= bolusTargetVolume ) { result = SALINE_BOLUS_STATE_IDLE; } // User is aborting saline bolus else if ( TRUE == salineBolusAbortRequested ) { salineBolusAbortRequested = FALSE; result = SALINE_BOLUS_STATE_IDLE; } } // Are we stopping the bolus? if ( result != SALINE_BOLUS_STATE_IN_PROGRESS ) { // Hard stop blood and dialysate pumps signalBloodPumpHardStop(); signalDialInPumpHardStop(); // Send last saline bolus data salineBolusBroadcastTimerCtr = SALINE_BOLUS_DATA_PUB_INTERVAL; publishSalineBolusData(); sendTreatmentLogEventData( SALINE_BOLUSES_CHANGE_EVENT, bolusSalineVolumeDelivered_mL, totalSalineVolumeDelivered_mL ); bolusSalineVolumeDelivered_mL = 0.0; // Dialysis back to UF state *dialysisState = DIALYSIS_UF_STATE; // End dialyzer bypass and resume dialysis if no alarms triggered if ( FALSE == errorFound ) { // Resume UF if appropriate if ( TRUE == autoResumeUF ) { autoResumeUF = FALSE; currentUFState = UF_RUNNING_STATE; //Set substate for event setCurrentSubState( (U32)DIALYSIS_UF_STATE ); setCurrent4thLevelState( (U32)currentUFState ); sendOperationStatusEvent(); } signalInitiatePressureStabilization( USE_NORMAL_STABILIZATION_PERIOD ); // Resume dialysis transitionToDialysis(); } } return result; } /*********************************************************************//** * @brief * The handleSalineBolusMaxDeliveredState function handles the max saline delivered * state of the saline bolus state machine. This is a terminal state. * @details Inputs: none * @details Outputs: * @param dialysisState next dialysis state * @return next saline bolus state *************************************************************************/ static SALINE_BOLUS_STATE_T handleSalineBolusMaxDeliveredState( DIALYSIS_STATE_T *dialysisState ) { SALINE_BOLUS_STATE_T result = SALINE_BOLUS_STATE_MAX_DELIVERED; // This is a terminal state for a given treatment - no more saline may be delivered to patient // If we get here, pop back to UF *dialysisState = DIALYSIS_UF_STATE; return result; } /*********************************************************************//** * @brief * The publishSalineBolusData function handles the max saline delivered * state of the saline bolus state machine. This is a terminal state. * @details Inputs: none * @details Outputs: * @param dialysisState next dialysis state * @return next saline bolus state *************************************************************************/ static void publishSalineBolusData( void ) { if ( ++salineBolusBroadcastTimerCtr >= SALINE_BOLUS_DATA_PUB_INTERVAL ) { SALINE_BOLUS_DATA_PAYLOAD_T data; data.tgtSalineVolumeMl = getTreatmentParameterU32( TREATMENT_PARAM_SALINE_BOLUS_VOLUME ); data.cumSalineVolumeMl = totalSalineVolumeDelivered_mL; data.bolSalineVolumeMl = bolusSalineVolumeDelivered_mL; broadcastData( MSG_ID_SALINE_BOLUS_DATA, COMM_BUFFER_OUT_CAN_HD_BROADCAST, (U08*)&data, sizeof( SALINE_BOLUS_DATA_PAYLOAD_T ) ); salineBolusBroadcastTimerCtr = 0; } } /*********************************************************************//** * @brief * The checkUFControl function checks ultrafiltration control to ensure measured * UF volume does not deviate too far from the UF reference volume (indicating * poor UF control). * @details Inputs: none * @details Outputs: none * @return none *************************************************************************/ static void checkUFControl( void ) { // Check total UF volume error if ( ( fabs( refUFVolume - measUFVolume ) ) > MAX_UF_VOLUME_ACCURACY_ERROR_ML ) { #ifndef _RELEASE_ if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_ULTRAFILTRATION_ALARMS ) != SW_CONFIG_ENABLE_VALUE ) #endif { SET_ALARM_WITH_2_F32_DATA( ALARM_ID_HD_UF_VOLUME_ACCURACY_ERROR, refUFVolume, measUFVolume ); } } } /*********************************************************************//** * @brief * The updateUFVolumes function updates the measured ultrafiltration volume based on * the latest load cell weight for the currently used reservoir. Updated UF volumes * are then sent to the dialysate outlet pump controller. * @details Inputs: measUFVolumeFromPriorReservoirs, resStartVolume[], load cell weight * @details Outputs: refUFVolume, measUFVolume * @return none *************************************************************************/ static void updateUFVolumes( void ) { DG_RESERVOIR_ID_T activeRes = getDGActiveReservoir(); LOAD_CELL_ID_T loadCell = ( activeRes == DG_RESERVOIR_1 ? LOAD_CELL_RESERVOIR_1_PRIMARY : LOAD_CELL_RESERVOIR_2_PRIMARY ); F32 latestResVolume = getLoadCellWeight( loadCell ); F32 deltaVolume = latestResVolume - resLastVolume[ activeRes ]; // ensure volume change is not too excessive - indication that load cell was impacted by some kind of shock if ( fabs(deltaVolume) > MAX_ACTIVE_LOAD_CELL_CHANGE_G ) { #ifndef _RELEASE_ if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_ULTRAFILTRATION_ALARMS ) != SW_CONFIG_ENABLE_VALUE ) #endif { ALARM_ID_T deltaAlarm = ( getDGActiveReservoir() == DG_RESERVOIR_1 ? ALARM_ID_HD_LOAD_CELL_ACCELERATION_RES_1_ALARM : ALARM_ID_HD_LOAD_CELL_ACCELERATION_RES_2_ALARM ); SET_ALARM_WITH_1_F32_DATA( deltaAlarm, deltaVolume ); } } else { // Calculate UF volumes and provide to dialysate outlet pump controller measUFVolume = measUFVolumeFromPriorReservoirs + ( latestResVolume - resStartVolume[ activeRes ] ); resFinalVolume[ activeRes ] = latestResVolume; setDialOutUFVolumes( refUFVolume, measUFVolume ); } } /*********************************************************************//** * @brief * The setStartReservoirVolume function updates the baseline volume of the * next reservoir to be drawn from before it is switched to (i.e. while it * is the inactive reservoir) in order to get a more stable volume. * @details Inputs: active reservoir, load cell reading from inactive reservoir * @details Outputs: resStartVolume[] * @param reservoirID reservoir ID to update the baseline volume. * @return none *************************************************************************/ void setStartReservoirVolume( DG_RESERVOIR_ID_T reservoirID ) { F32 resVolume = getReservoirWeightLargeFilter( reservoirID ); // Set starting baseline volume for next reservoir before we switch to it resStartVolume[ reservoirID ] = resVolume; resFinalVolume[ reservoirID ] = resVolume; resStartRefVolume[ reservoirID ] = refUFVolume; checkLoadCellsStablePrimaryBackupDriftOutOfRange( reservoirID, RESERVOIR_STEADY_CYCLE_START ); } /*********************************************************************//** * @brief * The signalReservoirsSwitched function informs this module that the * reservoirs have been switched. The UF volume from prior reservoirs is * tentatively added to with a load cell reading of the inactive reservoir. * @details Inputs: resFinalVolume[], resStartVolume[] * @details Outputs: measUFVolumeFromPriorReservoirs * @return none *************************************************************************/ void signalReservoirsSwitched( void ) { DG_RESERVOIR_ID_T inactiveRes = getDGInactiveReservoir(); // Update UF volume from prior reservoirs per tentative res volume for last reservoir measUFVolumeFromPriorReservoirs += ( resFinalVolume[ inactiveRes ] - resStartVolume[ inactiveRes ] ); // Record final UF ref volume for the spent reservoir resFinalRefVolume[ inactiveRes ] = refUFVolume; } /*********************************************************************//** * @brief * The setFinalReservoirVolume function updates the UF volume from prior reservoirs * with a more stable final reservoir volume of the prior reservoir after it * had a moment to settle. * @details Inputs: active reservoir, load cell reading from inactive reservoir * @details Outputs: measUFVolumeFromPriorReservoirs, resFinalVolume[], resStartVolume[] * @return none *************************************************************************/ void setFinalReservoirVolume( void ) { DG_RESERVOIR_ID_T inactiveRes = getDGInactiveReservoir(); F32 resVolume = getReservoirWeightLargeFilter( inactiveRes ); F32 resUFTimeInMs = (F32)getLastReservoirUFTimeInMs(); F32 resUFTimeInMin = ( resUFTimeInMs / (F32)( SEC_PER_MIN * MS_PER_SECOND ) ); F32 resExpUFVolumeInMl = resFinalRefVolume[ inactiveRes ] - resStartRefVolume[ inactiveRes ]; F32 resExpUFRate = resExpUFVolumeInMl / resUFTimeInMin; F32 uFResVolumeInMl; F32 uFMeasRate; // Update UF volume from prior reservoirs per final res volume for last reservoir a bit after we have switched and reservoir has settled measUFVolumeFromPriorReservoirs -= ( resFinalVolume[ inactiveRes ] - resStartVolume[ inactiveRes ] ); resFinalVolume[ inactiveRes ] = resVolume; uFResVolumeInMl = ( resFinalVolume[ inactiveRes ] - resStartVolume[ inactiveRes ] ); measUFVolumeFromPriorReservoirs += uFResVolumeInMl; // Calc UF rate from reservoir weight change uFMeasRate = uFResVolumeInMl / resUFTimeInMin; SEND_EVENT_WITH_2_F32_DATA( HD_EVENT_RSRVR_UF_VOLUME_AND_TIME, uFResVolumeInMl, resUFTimeInMin ) SEND_EVENT_WITH_2_F32_DATA( HD_EVENT_RSRVR_UF_RATE, uFMeasRate, resExpUFRate ) // Check redundant load cells checkLoadCellsStablePrimaryBackupDriftOutOfRange( inactiveRes, RESERVOIR_STEADY_CYCLE_FINAL ); } /*********************************************************************//** * @brief * The updateReservoirVolumes function updates the reservoir volumes based * on new load cell readings. * @details Inputs: none * @details Outputs: resCurrVolume[], resLastVolume[] * @param res1Vol new volume for reservoir 1 * @param res2Vol new volume for reservoir 2 * @return none *************************************************************************/ void updateReservoirVolumes( F32 res1Vol, F32 res2Vol ) { resLastVolume[ DG_RESERVOIR_1 ] = resCurrVolume[ DG_RESERVOIR_1 ]; resLastVolume[ DG_RESERVOIR_2 ] = resCurrVolume[ DG_RESERVOIR_2 ]; resCurrVolume[ DG_RESERVOIR_1 ] = res1Vol; resCurrVolume[ DG_RESERVOIR_2 ] = res2Vol; } /*********************************************************************//** * @brief * The getReservoirUltrafiltrationVol function returns the ultrafiltration * volume of a reservoir. * @details Inputs: none * @details Outputs: resStartVolume[], resFinalVolume[] * @param reservoirID reservoir ID to calculate the ultrafiltration volume * @return ultrafiltration volume of the reservoir *************************************************************************/ F32 getReservoirUltrafiltrationVol( DG_RESERVOIR_ID_T reservoirID ) { return ( resFinalVolume[ reservoirID ] - resStartVolume[ reservoirID ] ); } /*********************************************************************//** * @brief * The checkLoadCellsStablePrimaryBackupDriftOutOfRange function checks the * load cells' primary and backup drift when the reservoir level has been stable * for greater than large filter time. * @details Inputs: none * @details Outputs: none * @param reservoirID which is the reservoir ID that is range checked * @param cycle - final or start reading for reservoir * @return none *************************************************************************/ static void checkLoadCellsStablePrimaryBackupDriftOutOfRange( DG_RESERVOIR_ID_T reservoirID, RESERVOIR_STEADY_CYCLE_T cycle ) { F32 loadCellPrimaryWeight, loadCellBackupWeight; F32 loadCellPreviousDrift, loadCellCurrentDrift; F32 driftDiff = 0.0F; U16 lcPrimaryIndex, lcBackupIndex; HD_EVENT_ID_T event; if ( DG_RESERVOIR_1 == reservoirID ) { lcPrimaryIndex = LOAD_CELL_RESERVOIR_1_PRIMARY; lcBackupIndex = LOAD_CELL_RESERVOIR_1_BACKUP; event = ( RESERVOIR_STEADY_CYCLE_START == cycle ? HD_EVENT_RSRVR_1_LOAD_CELL_START_VALUES : HD_EVENT_RSRVR_1_LOAD_CELL_END_VALUES ); } else { lcPrimaryIndex = LOAD_CELL_RESERVOIR_2_PRIMARY; lcBackupIndex = LOAD_CELL_RESERVOIR_2_BACKUP; event = ( RESERVOIR_STEADY_CYCLE_START == cycle ? HD_EVENT_RSRVR_2_LOAD_CELL_START_VALUES : HD_EVENT_RSRVR_2_LOAD_CELL_END_VALUES ); } loadCellPrimaryWeight = getReservoirWeightLargeFilter( reservoirID ); loadCellBackupWeight = getReservoirBackupWeightLargeFilter( reservoirID ); loadCellCurrentDrift = loadCellPrimaryWeight - loadCellBackupWeight; SEND_EVENT_WITH_2_F32_DATA( event, loadCellPrimaryWeight, loadCellBackupWeight ) if ( ( RESERVOIR_STEADY_CYCLE_FINAL == cycle ) && ( lcLastSteadyWeight[ RESERVOIR_STEADY_CYCLE_START ][ lcPrimaryIndex ] > ( LOAD_CELL_ILLEGAL_WEIGHT_VALUE + 1 ) ) ) { // Start Weight has been previously saved, ok to test loadCellPreviousDrift = lcLastSteadyWeight[ RESERVOIR_STEADY_CYCLE_START ][ lcPrimaryIndex ] - lcLastSteadyWeight[ RESERVOIR_STEADY_CYCLE_START ][ lcBackupIndex ]; driftDiff = fabs ( loadCellCurrentDrift - loadCellPreviousDrift ); } // Save latest reading for next test time lcLastSteadyWeight[ cycle ][ lcPrimaryIndex ] = loadCellPrimaryWeight; lcLastSteadyWeight[ cycle ][ lcBackupIndex ] = loadCellBackupWeight; #ifndef _RELEASE_ if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_ULTRAFILTRATION_ALARMS ) != SW_CONFIG_ENABLE_VALUE ) #endif { // Check for drift out of range if ( driftDiff > LOAD_CELL_PRIMARY_BACKUP_MAX_ALLOWED_DRIFT_GRAMS ) { SET_ALARM_WITH_2_F32_DATA( ALARM_ID_HD_LOAD_CELL_PRIMARY_BACKUP_DRIFT_OUT_OF_RANGE, loadCellCurrentDrift, loadCellPreviousDrift ) } } } /**@}*/