/************************************************************************** * * Copyright (c) 2025-2026 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 BalancingChamber.c * * @author (last) Jashwant Gantyada * @date (last) 05-Mar-2026 * * @author (original) Vinayakam Mani * @date (original) 28-Jan-2025 * ***************************************************************************/ #include "BalancingChamber.h" #include "Conductivity.h" #include "ConcentratePumps.h" #include "DialysatePumps.h" #include "FpgaDD.h" #include "Heaters.h" #include "ModeGenDialysate.h" #include "ModeStandby.h" #include "Messaging.h" #include "OperationModes.h" #include "Pressure.h" #include "TaskGeneral.h" #include "TDInterface.h" #include "Temperature.h" #include "TestSupport.h" #include "Valves.h" /** * @addtogroup BalancingChamber * @{ */ // ********** private definitions ********** #define BAL_CHAMBER_DATA_PUBLISH_INTERVAL ( 250 / TASK_GENERAL_INTERVAL ) ///< Interval (ms/task time) at which the balancing chamber data published. #define BAL_CHAMBER_FILL_PRES_DROP_MS ( 200 / TASK_GENERAL_INTERVAL ) ///< Time (ms/tasktime) to confirm the balancing chamber filling started and corrosponding valves opened. #define BAL_CHAMBER_FILL_COMPLETE_MS ( 300 / TASK_GENERAL_INTERVAL ) ///< Time (ms/tasktime) to confirm the balancing chamber fill completed and pressure is within range #define SPENT_FILL_COMPLETE_PRES 25.0F ///< Spent fill complete pressure (PSI) for Diener 2000 pump. #define SPENT_FILL_COMPLETE_PRES_QD_LOW_PSIG 3.5F ///< Spent fill complete pressure (PSI) when Diener 1000 and Qd <= 200. #define SPENT_FILL_COMPLETE_PRES_QD_MID_PSIG 15.0F ///< Spent fill complete pressure (PSI) when Diener 1000 and 200 < Qd <= 400. #define SPENT_FILL_COMPLETE_PRES_QD_HIGH_PSIG 20.0F ///< Spent fill complete pressure (PSI) when Diener 1000 and Qd > 400. #define QD_THRESHOLD_LOW_MLPM 200.0F ///< Qd threshold (mL/min) below which low pressure limit applies. #define QD_THRESHOLD_HIGH_MLPM 400.0F ///< Qd threshold (mL/min) below which mid pressure limit applies. #define SPENT_DIFF_COUNT_ZERO 0 ///< Zero count difference for spent side fill comparing target count #define SPENT_DIFF_COUNT_DEADBAND 1 ///< Stop changing D48 pump speed when fill difference reaches close to target count #define D48_SPEED_ADJUST_FACTOR 0.5F ///< D48 speed adjustment factor ( 50% of speed adjustment = 0.5) #define D48_SPEED_RANGE_LIMIT 0.25F ///< D48 speed adjustment range check limit ( D48 speed can vary +/-25% of initial calculated speed) /// Payload record structure for balancing chamber switch only request typedef struct { U32 startStop; ///< balancing chamber switching only start:1 and stop: 0 F32 flowrate; ///< dialysate flowrate in ml/min } BC_SWITCHING_ONLY_START_CMD_PAYLOAD_T; // ********** private data ********** static BAL_CHAMBER_EXEC_STATE_T balChamberExecState; ///< Current balancing chamber executive state. static OVERRIDE_F32_T balChamberSwitchingFreq; ///< Balancing chamber switching rate ( switches per min) based on the dialysate flow rate ( overrideable) static U32 balChamberSwitchingPeriod; ///< Periodic balancing chamber switching time in task interval count. static U32 balChamberValveClosePeriod; ///< Close balancing chamber valves with the defined time prior switching state. static U32 currentBalChamberSwitchingCounter; ///< Counter (in task interval) to monitor the timing spent during balancing chamber fill/drain operation. static BOOL isBalChamberFillInProgress; ///< Flag indicating balancing chamber fill/drain is in progress. static BOOL isPressureStabilizedDuringFill; ///< Flag indicating that the pressure is stablized due to fill complete. static BAL_CHAMBER_SW_STATE_T balChamberSWState; ///< Current balancing chamber switching state ( state 1 or state 2). static U32 balChamberDataPublicationTimerCounter; ///< Used to schedule balancing chamber data publication to CAN bus. static U32 balChamberFillPressureDropCounter; ///< Counter to check balancing chamber valves opened and there by pressure drop is seen. static OVERRIDE_U32_T balChamberDataPublishInterval; ///< Balancing chamber data publish interval. static BOOL balanceChamberSwitchingOnly; ///< Balancing chamber switching without any pressure check and dosing delivery. static BOOL isPressureDroppedDuringFill; ///< Flag indicating that the pressure is dropped due to BC fill (state 1 or state 2) in progress condition. static BOOL isFirstCycleBCSwitchingCompleted; ///< Flag indicating that first time balancing chamber swithcing is done to trigger alarms from next cycle onwards. static U32 balChamberFillCompleteStablePressureCounter; ///< Counter to check balancing chamber fill complete and stable pressure is met. static OVERRIDE_F32_T acidDoseVolume; ///< Acid concentrate volume in ml ( overrideable). static OVERRIDE_F32_T bicarbDoseVolume; ///< Bicarb concentrate volume in ml ( overrideable). static F32 lastTdDialysateFlowrate; ///< Previous TD dialysate flow rate static F32 freshDialPressure; ///< Fresh side dialysate pressure static F32 spentDialPressure; ///< Spent side dialysate pressure static F32 prevSpentDialPressure; ///< Previous spent side dialysate pressure static F32 lastPrevSpentDialPressure; ///< Last previous spent side dialysate pressure static U32 currentBalChamberFillCounter; ///< Counter (in task interval) to monitor the timing spent for the spent side fill operation. static S32 diffSpentFillCompleteCount; ///< Difference between spent target fill to actual fill count static BOOL isSpentFillComplete; // ********** private function prototypes ********** static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState1FillStart( void ); static BAL_CHAMBER_EXEC_STATE_T handleBalChamberConcentrateControl(void); static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState1ValvesClose( void ); static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState1FillEnd(void); static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState2FillStart( void ); static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState2ValvesClose( void ); static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState2FillEnd(void); static void publishBalChamberData( void ); static U32 getBalChamberDataPublishInterval( void ); static void checkSpentFillComplete( F32 spentDialPressure ); /*********************************************************************//** * @brief * The initBalanceChamber function initializes the balancing chamber unit. * @details \b Inputs: none * @details \b Outputs: unit variables initialized * @return none *************************************************************************/ void initBalanceChamber( void ) { balChamberExecState = BAL_CHAMBER_STATE_START; balChamberSWState = BAL_CHAMBER_SW_STATE1; balChamberSwitchingFreq.data = 0.0F; balChamberSwitchingFreq.ovData = 0.0F; balChamberSwitchingFreq.ovInitData = 0.0F; balChamberSwitchingFreq.override = OVERRIDE_RESET; balChamberDataPublishInterval.data = BAL_CHAMBER_DATA_PUBLISH_INTERVAL; balChamberDataPublishInterval.ovData = BAL_CHAMBER_DATA_PUBLISH_INTERVAL; balChamberDataPublishInterval.ovInitData = 0; balChamberDataPublishInterval.override = OVERRIDE_RESET; acidDoseVolume.data = DEFAULT_ACID_VOLUME_ML; acidDoseVolume.ovData = DEFAULT_ACID_VOLUME_ML; acidDoseVolume.ovInitData = 0.0F; acidDoseVolume.override = OVERRIDE_RESET; bicarbDoseVolume.data = DEFAULT_BICARB_VOLUME_ML; bicarbDoseVolume.ovData = DEFAULT_BICARB_VOLUME_ML; bicarbDoseVolume.ovInitData = 0.0F; bicarbDoseVolume.override = OVERRIDE_RESET; balanceChamberSwitchingOnly = FALSE; balChamberSwitchingPeriod = 0; balChamberValveClosePeriod = 0; isBalChamberFillInProgress = FALSE; currentBalChamberSwitchingCounter = 0; isPressureStabilizedDuringFill = FALSE; lastTdDialysateFlowrate = 0.0F; balChamberDataPublicationTimerCounter = 0; balChamberFillPressureDropCounter = 0; balChamberFillCompleteStablePressureCounter = 0; isFirstCycleBCSwitchingCompleted = FALSE; isPressureDroppedDuringFill = FALSE; freshDialPressure = 0.0F; spentDialPressure = 0.0F; prevSpentDialPressure = 0.0F; lastPrevSpentDialPressure = 0.0F; currentBalChamberFillCounter = 0; diffSpentFillCompleteCount = 0; isSpentFillComplete = FALSE; } /*********************************************************************//** * @brief * The transitionToBalChamberFill function prepares for transition to * balancing chamber fill/switching operations. * @details \b Inputs: none * @details \b Outputs: balChamberSwitchingFreq ,balChamberSwitchingPeriod * @return none *************************************************************************/ void transitionToBalChamberFill( void ) { initBalanceChamber(); updateBalChamberSwitchingPeriod(); } /*********************************************************************//** * @brief * The updateBalChamberSwitchingPeriod function updates the periodic * balancing chamber switching time based on the dialysis flow rate. * @details \b Inputs: Dialysis flow rate. * @details \b Outputs: balChamberSwitchingFreq,balChamberSwitchingPeriod * @return none *************************************************************************/ void updateBalChamberSwitchingPeriod( void ) { F32 tdDialysateFlowrate = getTDDialysateFlowrate(); if ( lastTdDialysateFlowrate != tdDialysateFlowrate ) { // update the balancing chamber switching frequency balChamberSwitchingFreq.data = tdDialysateFlowrate / BAL_CHAMBER_FILL_VOLUME_ML; //update the switching period in task interval for balancing chamber fill timeout check balChamberSwitchingPeriod = (U32)( (F32)SEC_PER_MIN / getBalChamberSwitchingFreq() * ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ); // finish the balancing chamber fill 50 ms prior completing the regular cycle time. balChamberSwitchingPeriod -= 1; //Update last td dialysate flow rate lastTdDialysateFlowrate = tdDialysateFlowrate; //Reset the BC switching flag for new Qd. isFirstCycleBCSwitchingCompleted = FALSE; //Update heater control on dialysate flow change signalHeaterControlOnQDUpdate( D5_HEAT ); //Testing balChamberValveClosePeriod = balChamberSwitchingPeriod; balChamberValveClosePeriod -= 1; // Close valves prior 50 msecond for testing } } /*********************************************************************//** * @brief * The execBalancingChamberControl function executes the balancing chamber state machine. * @details \b Inputs: balChamberExecState * @details \b Outputs: balChamberExecState * @details \b Alarm: ALARM_ID_DD_SOFTWARE_FAULT when wrong balancing chamber state invoked. * @return current state. *************************************************************************/ U32 execBalancingChamberControl( void ) { // Increment counter indicating fill is in progress. currentBalChamberSwitchingCounter += 1; currentBalChamberFillCounter += 1; // execute current balancing chamber exec state switch ( balChamberExecState ) { case BAL_CHAMBER_STATE_START: balChamberExecState = BAL_CHAMBER_STATE1_FILL_START; break; case BAL_CHAMBER_STATE1_FILL_START: balChamberExecState = handleBalChamberState1FillStart(); break; case BAL_CHAMBER_STATE1_BICARB_ACID_DOSING_CNTRL: balChamberExecState = handleBalChamberConcentrateControl(); break; case BAL_CHAMBER_STATE1_VALVES_CLOSE: balChamberExecState = handleBalChamberState1ValvesClose(); break; case BAL_CHAMBER_STATE1_FILL_END: balChamberExecState = handleBalChamberState1FillEnd(); break; case BAL_CHAMBER_STATE2_FILL_START: balChamberExecState = handleBalChamberState2FillStart(); break; case BAL_CHAMBER_STATE2_BICARB_ACID_DOSING_CNTRL: balChamberExecState = handleBalChamberConcentrateControl(); break; case BAL_CHAMBER_STATE2_VALVES_CLOSE: balChamberExecState = handleBalChamberState2ValvesClose(); break; case BAL_CHAMBER_STATE2_FILL_END: balChamberExecState = handleBalChamberState2FillEnd(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_BAL_CHAMBER_INVALID_EXEC_STATE, balChamberExecState ) balChamberExecState = BAL_CHAMBER_STATE_START; break; } //Publish balancing chamber data publishBalChamberData(); return balChamberExecState; } /*********************************************************************//** * @brief * The valveControlForBCState1FillStart function actuates the valve combination * for state 1 fill/drain process. * @details \b Inputs: none * @details \b Outputs: valve states * @return none. *************************************************************************/ static void valveControlForBCState1FillStart( void ) { // Close balancing chamber valve combinations D23,D20 and D21,D26 setValveState( D23_VALV, VALVE_STATE_CLOSED ); setValveState( D20_VALV, VALVE_STATE_CLOSED ); setValveState( D21_VALV, VALVE_STATE_CLOSED ); setValveState( D26_VALV, VALVE_STATE_CLOSED ); //Open balancing chamber valve combinations D19,D24 and D25,D22 setValveState( D19_VALV, VALVE_STATE_OPEN ); setValveState( D24_VALV, VALVE_STATE_OPEN ); setValveState( D25_VALV, VALVE_STATE_OPEN ); setValveState( D22_VALV, VALVE_STATE_OPEN ); } /*********************************************************************//** * @brief * The valveControlForBCState1FillEnd function closes the valve opened * for state 1 fill/drain process. * @details \b Inputs: none * @details \b Outputs: valve states * @return none. *************************************************************************/ static void valveControlForBCState1FillEnd( void ) { //Close balancing chamber valve combinations D19,D24 and D25,D22 setValveState( D19_VALV, VALVE_STATE_CLOSED ); setValveState( D24_VALV, VALVE_STATE_CLOSED ); setValveState( D25_VALV, VALVE_STATE_CLOSED ); setValveState( D22_VALV, VALVE_STATE_CLOSED ); } /*********************************************************************//** * @brief * The valveControlForBCState2FillStart function actuates the valve combination * for state 2 fill/drain process. * @details \b Inputs: none * @details \b Outputs: valve states * @return none. *************************************************************************/ static void valveControlForBCState2FillStart( void ) { //Close balancing chamber valve combinations D19,D24 and D25,D22 setValveState( D19_VALV, VALVE_STATE_CLOSED ); setValveState( D24_VALV, VALVE_STATE_CLOSED ); setValveState( D25_VALV, VALVE_STATE_CLOSED ); setValveState( D22_VALV, VALVE_STATE_CLOSED ); // Open balancing chamber valve combinations D23,D20 and D21,D26 setValveState( D23_VALV, VALVE_STATE_OPEN ); setValveState( D20_VALV, VALVE_STATE_OPEN ); setValveState( D21_VALV, VALVE_STATE_OPEN ); setValveState( D26_VALV, VALVE_STATE_OPEN ); } /*********************************************************************//** * @brief * The valveControlForBCState2FillEnd function closes the valve opened * for state 2 fill/drain process. * @details \b Inputs: none * @details \b Outputs: valve states * @return none. *************************************************************************/ static void valveControlForBCState2FillEnd( void ) { // Close balancing chamber valve combinations D23,D20 and D21,D26 setValveState( D23_VALV, VALVE_STATE_CLOSED ); setValveState( D20_VALV, VALVE_STATE_CLOSED ); setValveState( D21_VALV, VALVE_STATE_CLOSED ); setValveState( D26_VALV, VALVE_STATE_CLOSED ); } /*********************************************************************//** * @brief * The valveControlForBCClosedState function closes the all balancing * chamber valves. * @details \b Inputs: none * @details \b Outputs: valve states * @return none. *************************************************************************/ void valveControlForBCClosedState( void ) { // Close balancing chamber valve combinations D23,D20 and D21,D26 setValveState( D23_VALV, VALVE_STATE_CLOSED ); setValveState( D20_VALV, VALVE_STATE_CLOSED ); setValveState( D21_VALV, VALVE_STATE_CLOSED ); setValveState( D26_VALV, VALVE_STATE_CLOSED ); //Close balancing chamber valve combinations D19,D24 and D25,D22 setValveState( D19_VALV, VALVE_STATE_CLOSED ); setValveState( D24_VALV, VALVE_STATE_CLOSED ); setValveState( D25_VALV, VALVE_STATE_CLOSED ); setValveState( D22_VALV, VALVE_STATE_CLOSED ); } /*********************************************************************//** * @brief * The valveControlForBCOpenState function open the all balancing * chamber valves. * @details \b Inputs: none * @details \b Outputs: valve states * @return none. *************************************************************************/ void valveControlForBCOpenState( void ) { // Open balancing chamber valve combinations D23,D20 and D21,D26 setValveState( D23_VALV, VALVE_STATE_OPEN ); setValveState( D20_VALV, VALVE_STATE_OPEN ); setValveState( D21_VALV, VALVE_STATE_OPEN ); setValveState( D26_VALV, VALVE_STATE_OPEN ); //Open balancing chamber valve combinations D19,D24 and D25,D22 setValveState( D19_VALV, VALVE_STATE_OPEN ); setValveState( D24_VALV, VALVE_STATE_OPEN ); setValveState( D25_VALV, VALVE_STATE_OPEN ); setValveState( D22_VALV, VALVE_STATE_OPEN ); } /*********************************************************************//** * @brief * The valveControlForBCFreshSideOnlyOpenState function opens the fresh side * balancing chamber valves and closes the spent side valves. * @details \b Inputs: none * @details \b Outputs: valve states * @return none. *************************************************************************/ void valveControlForBCFreshSideOnlyOpenState( void ) { // Open fresh balancing chamber valves D19,D20,D21,D22 setValveState( D19_VALV, VALVE_STATE_OPEN ); setValveState( D20_VALV, VALVE_STATE_OPEN ); setValveState( D21_VALV, VALVE_STATE_OPEN ); setValveState( D22_VALV, VALVE_STATE_OPEN ); // Close spent side valves D23,D24,D25,D26 setValveState( D23_VALV, VALVE_STATE_CLOSED ); setValveState( D25_VALV, VALVE_STATE_CLOSED ); setValveState( D24_VALV, VALVE_STATE_CLOSED ); setValveState( D26_VALV, VALVE_STATE_CLOSED ); } /*********************************************************************//** * @brief * The handleBalChamberState1FillStart function handles the balancing chamber * state 1 fill and time to fill chamber counter being updated. * @details \b Inputs: Pressure * @details \b Outputs: valve states * @details \b Alarm: ALARM_ID_DD_BC_STATE1_FILL_START_PRESSURE_OUT_OF_RANGE * when pressure is not in range during balancing chamber fill start. * @return next balancing chamber state. *************************************************************************/ static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState1FillStart( void ) { BAL_CHAMBER_EXEC_STATE_T state = BAL_CHAMBER_STATE1_FILL_START; currentBalChamberSwitchingCounter = 0; balChamberSWState = BAL_CHAMBER_SW_STATE1; currentBalChamberFillCounter = 0; isBalChamberFillInProgress = FALSE; isPressureStabilizedDuringFill = FALSE; isPressureDroppedDuringFill = FALSE; isSpentFillComplete = FALSE; lastPrevSpentDialPressure = 0.0F; prevSpentDialPressure = 0.0F; F32 acidVolume = getF32OverrideValue( &acidDoseVolume ); F32 bicarbVolume = getF32OverrideValue( &bicarbDoseVolume ); freshDialPressure = getFilteredPressure( D18_PRES ); spentDialPressure = getFilteredPressure( D51_PRES ); // Check fresh dialysate pressure in range or BC switch only flag set or BC pressure alarms are disabled if ( ( ( freshDialPressure >= FRESH_DIAL_PRESSURE_MIN_PSIG ) && ( freshDialPressure <= FRESH_DIAL_PRESSURE_MAX_PSIG ) ) || ( TRUE == getBalChamberSwitchingOnlyStatus() ) || ( TRUE == getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) ) ) { //Valve control for state 1 fill valveControlForBCState1FillStart(); // Update fill status flag to true isBalChamberFillInProgress = TRUE; // Deliver dosing during generate dialysate mode if ( TRUE != getBalChamberSwitchingOnlyStatus() ) { // start acid and bicarb pump with the expected quantity setConcentratePumpTargetSpeed( D11_PUMP, CONCENTRATE_PUMP_MAX_SPEED, acidVolume ); setConcentratePumpTargetSpeed( D10_PUMP, CONCENTRATE_PUMP_MAX_SPEED, bicarbVolume ); requestConcentratePumpOn( D11_PUMP ); requestConcentratePumpOn( D10_PUMP ); } state = BAL_CHAMBER_STATE1_BICARB_ACID_DOSING_CNTRL; } else { if ( getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) != TRUE ) { //Alarm when pressure is not in range SET_ALARM_WITH_2_F32_DATA( ALARM_ID_DD_BC_STATE1_FILL_START_PRESSURE_OUT_OF_RANGE, freshDialPressure, spentDialPressure ); } } return state; } /*********************************************************************//** * @brief * The handleBalChamberConcentrateControl function handles the Acid and Bicarb * concentrate dosing and checks the conductivity of the dialysate for the treatment. * @details \b Inputs: balChamberSWState , Concentrate volume * @details \b Outputs: isPressureDroppedDuringFill, state * @return next balancing chamber state. *************************************************************************/ static BAL_CHAMBER_EXEC_STATE_T handleBalChamberConcentrateControl( void ) { BAL_CHAMBER_EXEC_STATE_T state; freshDialPressure = getFilteredPressure( D18_PRES ); spentDialPressure = getFilteredPressure( D51_PRES ); if ( BAL_CHAMBER_SW_STATE1 == balChamberSWState ) { state = BAL_CHAMBER_STATE1_BICARB_ACID_DOSING_CNTRL; } else { state = BAL_CHAMBER_STATE2_BICARB_ACID_DOSING_CNTRL; } // Pressure drop check during fill process helps to find if there is any issue with valves opening if ( ( freshDialPressure <= BC_FRESH_FILL_PRESSURE_PSIG ) && ( spentDialPressure <= BC_SPENT_FILL_PRESSURE_PSIG ) ) { if ( ++balChamberFillPressureDropCounter >= BAL_CHAMBER_FILL_PRES_DROP_MS ) { isPressureDroppedDuringFill = TRUE; } } // On dosing completion, transition to next state based on the current switching state if ( ( ( TRUE == isConcentratePumpDosingCompleted( D11_PUMP ) ) && ( TRUE == isConcentratePumpDosingCompleted( D10_PUMP ) ) ) || ( TRUE == getBalChamberSwitchingOnlyStatus() ) ) { //Collect first sample of spent pressure lastPrevSpentDialPressure = prevSpentDialPressure; prevSpentDialPressure = spentDialPressure; if ( BAL_CHAMBER_SW_STATE1 == balChamberSWState ) { state = BAL_CHAMBER_STATE1_VALVES_CLOSE; } else { state = BAL_CHAMBER_STATE2_VALVES_CLOSE; } } return state; } /*********************************************************************//** * @brief * The handleBalChamberState1ValvesClose function check for the balancing chamber * fill complete and close the currently opened valves. * @details \b Inputs: currentBalChamberSwitchingCounter, spent and fresh dialysate pressure * @details \b Outputs: isPressureStalbilizedDuringFill,isBalChamberFillInProgress * @return next balancing chamber state. *************************************************************************/ static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState1ValvesClose( void ) { BAL_CHAMBER_EXEC_STATE_T state = BAL_CHAMBER_STATE1_VALVES_CLOSE; freshDialPressure = getFilteredPressure( D18_PRES ); spentDialPressure = getFilteredPressure( D51_PRES ); // Check fresh dialysate pressure back in range to indicate fresh fill complete. if ( ( freshDialPressure >= FRESH_DIAL_PRESSURE_MIN_PSIG ) && ( freshDialPressure <= FRESH_DIAL_PRESSURE_MAX_PSIG ) ) { if ( ++balChamberFillCompleteStablePressureCounter >= BAL_CHAMBER_FILL_COMPLETE_MS ) { // stabilized pressure indicating fresh side fill is complete isPressureStabilizedDuringFill = TRUE; } } // Spent side of balancing chamber fill is complete or not checkSpentFillComplete( spentDialPressure ); // Check both spent and fresh side fill is complete if ( ( TRUE == isSpentFillComplete ) && ( TRUE == isPressureStabilizedDuringFill ) || ( FALSE == isFirstCycleBCSwitchingCompleted ) ) { // close the state 1 opened valves valveControlForBCState1FillEnd(); isBalChamberFillInProgress = FALSE; //setDialysatePumpTargetRPM( D48_PUMP, getD48PumpSpeedForBCFill(), TRUE ); if ( TRUE == isFirstCycleBCSwitchingCompleted ) { setDialysatePumpTargetRPM( D48_PUMP, getD48PumpSpeedForBCFill(), TRUE ); } // Transition to next state state = BAL_CHAMBER_STATE1_FILL_END; } return state; } /*********************************************************************//** * @brief * The handleBalChamberState1FillEnd function check for the balancing chamber * switching period and transition to next state switching. * @details \b Inputs: currentBalChamberSwitchingCounter, balChamberSwitchingPeriod * @details \b Outputs: balChamberFillPressureDropCounter,balChamberFillCompleteStablePressureCounter * @details \b Alarm: ALARM_ID_DD_BC_STATE1_FILL_PRESSURE_DROP_OUT_OF_RANGE * when pressure drop is not in range during balancing chamber fill in progress. * @details \b Alarm: ALARM_ID_DD_BC_STATE1_FILL_END_PRESSURE_OUT_OF_RANGE * when pressure is not in range during balancing chamber fill complete. * @return next balancing chamber state. *************************************************************************/ static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState1FillEnd( void ) { BAL_CHAMBER_EXEC_STATE_T state = BAL_CHAMBER_STATE1_FILL_END; balChamberFillPressureDropCounter = 0; balChamberFillCompleteStablePressureCounter = 0; if ( TRUE != getBalChamberSwitchingOnlyStatus() ) { if ( getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) != TRUE ) { if ( ( TRUE != isPressureDroppedDuringFill ) && ( TRUE == isFirstCycleBCSwitchingCompleted ) ) { // When fill initiated, pressure is not dropped to the expected range, possible valve failures. SET_ALARM_WITH_2_F32_DATA( ALARM_ID_DD_BC_STATE1_FILL_PRESSURE_DROP_OUT_OF_RANGE, freshDialPressure, spentDialPressure ); } else if ( TRUE != isPressureStabilizedDuringFill ) { // Alarm when switching time expired, but still pressure not in range which indicates fill is not yet completed. SET_ALARM_WITH_2_F32_DATA( ALARM_ID_DD_BC_STATE1_FILL_END_PRESSURE_OUT_OF_RANGE, freshDialPressure, spentDialPressure ); } else { // Move to next state when pressure is in range. state = BAL_CHAMBER_STATE2_FILL_START; } } else { // Allow to proceed next state even though pressure is not stabilized. state = BAL_CHAMBER_STATE2_FILL_START; } } else { state = BAL_CHAMBER_STATE2_FILL_START; } return state; } /*********************************************************************//** * @brief * The handleBalChamberState2FillStart function handles the balancing chamber * state 2 fill and time to fill chamber counter being updated. * @details \b Inputs: fresh and spent dialysate pressure * @details \b Outputs: valve states * @details \b Alarm: ALARM_ID_DD_BC_STATE2_FILL_START_PRESSURE_OUT_OF_RANGE * when pressure is not in range during balancing chamber state 2 fill start. * @return next balancing chamber state. *************************************************************************/ static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState2FillStart( void ) { BAL_CHAMBER_EXEC_STATE_T state = BAL_CHAMBER_STATE2_FILL_START; currentBalChamberSwitchingCounter = 0; isBalChamberFillInProgress = FALSE; isPressureStabilizedDuringFill = FALSE; isPressureDroppedDuringFill = FALSE; balChamberSWState = BAL_CHAMBER_SW_STATE2; currentBalChamberFillCounter = 0; isSpentFillComplete = FALSE; lastPrevSpentDialPressure = 0.0F; prevSpentDialPressure = 0.0F; F32 acidVolume = getF32OverrideValue( &acidDoseVolume ); F32 bicarbVolume = getF32OverrideValue( &bicarbDoseVolume ); freshDialPressure = getFilteredPressure( D18_PRES ); spentDialPressure = getFilteredPressure( D51_PRES ); // Check fresh dialysate pressure in range if ( ( ( freshDialPressure >= FRESH_DIAL_PRESSURE_MIN_PSIG ) && ( freshDialPressure <= FRESH_DIAL_PRESSURE_MAX_PSIG ) ) || ( TRUE == getBalChamberSwitchingOnlyStatus() ) || ( TRUE == getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) ) ) { // Valve control for state 2 fill valveControlForBCState2FillStart(); // Update fill status flag to true isBalChamberFillInProgress = TRUE; // Deliver dosing during generate dialysate mode if ( TRUE != getBalChamberSwitchingOnlyStatus() ) { // start acid and bicarb pump with the expected quantity setConcentratePumpTargetSpeed( D11_PUMP, CONCENTRATE_PUMP_MAX_SPEED, acidVolume ); setConcentratePumpTargetSpeed( D10_PUMP, CONCENTRATE_PUMP_MAX_SPEED, bicarbVolume ); requestConcentratePumpOn( D11_PUMP ); requestConcentratePumpOn( D10_PUMP ); } state = BAL_CHAMBER_STATE2_BICARB_ACID_DOSING_CNTRL; } else { if ( getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) != TRUE ) { //Alarm when pressure is not in range SET_ALARM_WITH_2_F32_DATA( ALARM_ID_DD_BC_STATE2_FILL_START_PRESSURE_OUT_OF_RANGE, freshDialPressure, spentDialPressure ); } } return state; } /*********************************************************************//** * @brief * The handleBalChamberState2ValvesClose function check for the balancing chamber * fill complete and close the currently opened valves. * @details \b Inputs: currentBalChamberSwitchingCounter, spent and fresh dialysate pressure * @details \b Outputs: isPressureStalbilizedDuringFill,isBalChamberFillInProgress * @return next balancing chamber state. *************************************************************************/ static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState2ValvesClose( void ) { BAL_CHAMBER_EXEC_STATE_T state = BAL_CHAMBER_STATE2_VALVES_CLOSE; freshDialPressure = getFilteredPressure( D18_PRES ); spentDialPressure = getFilteredPressure( D51_PRES ); // Check fresh dialysate pressure back in range to indicate fill complete. if ( ( freshDialPressure >= FRESH_DIAL_PRESSURE_MIN_PSIG ) && ( freshDialPressure <= FRESH_DIAL_PRESSURE_MAX_PSIG ) ) { if ( ++balChamberFillCompleteStablePressureCounter >= BAL_CHAMBER_FILL_COMPLETE_MS ) { // stabilized pressure indicating fresh side fill is complete isPressureStabilizedDuringFill = TRUE; } } // Spent side of balancing chamber fill is complete or not checkSpentFillComplete( spentDialPressure ); // Check switching cycle time or pressure check for valve closure if ( ( TRUE == isSpentFillComplete ) && ( TRUE == isPressureStabilizedDuringFill ) || ( FALSE == isFirstCycleBCSwitchingCompleted ) ) { // close the valves valveControlForBCState2FillEnd(); isBalChamberFillInProgress = FALSE; //setDialysatePumpTargetRPM( D48_PUMP, getD48PumpSpeedForBCFill(), TRUE ); if ( TRUE == isFirstCycleBCSwitchingCompleted ) { setDialysatePumpTargetRPM( D48_PUMP, getD48PumpSpeedForBCFill(), TRUE ); } //Transition to next state state = BAL_CHAMBER_STATE2_FILL_END; } return state; } /*********************************************************************//** * @brief * The handleBalChamberState2FillEnd function check for the balancing chamber * switching period complete and transition to next state. * @details \b Inputs: currentBalChamberSwitchingCounter, balChamberSwitchingPeriod * @details \b Outputs: isPressureStalbilizedDuringFill,isBalChamberFillInProgress * @details \b Alarm: ALARM_ID_DD_BC_STATE2_FILL_PRESSURE_DROP_OUT_OF_RANGE * when pressure is not in range during balacing chamber state 2 fill in progress. * @details \b Alarm: ALARM_ID_DD_BC_STATE2_FILL_END_PRESSURE_OUT_OF_RANGE * when pressure is not in range during balacing chamber state 2 fill complete. * @return next balancing chamber state. *************************************************************************/ static BAL_CHAMBER_EXEC_STATE_T handleBalChamberState2FillEnd( void ) { BAL_CHAMBER_EXEC_STATE_T state = BAL_CHAMBER_STATE2_FILL_END; balChamberFillPressureDropCounter = 0; balChamberFillCompleteStablePressureCounter = 0; // Pressure alarm check if ( TRUE != getBalChamberSwitchingOnlyStatus() ) { if ( getTestConfigStatus( TEST_CONFIG_DD_DISABLE_BC_PRESSURE_ALARMS ) != TRUE ) { if ( ( TRUE != isPressureDroppedDuringFill ) && ( TRUE == isFirstCycleBCSwitchingCompleted ) ) { // When fill initiated, pressure is not dropped to the expected range, possible valve failures. SET_ALARM_WITH_2_F32_DATA( ALARM_ID_DD_BC_STATE2_FILL_PRESSURE_DROP_OUT_OF_RANGE, freshDialPressure, spentDialPressure ); } else if ( TRUE != isPressureStabilizedDuringFill ) { // Alarm when switching time expired, but still pressure not in range which indicates fill is not completed. SET_ALARM_WITH_2_F32_DATA( ALARM_ID_DD_BC_STATE2_FILL_END_PRESSURE_OUT_OF_RANGE, freshDialPressure, spentDialPressure ); } else { // Move to next state when pressure is in range. state = BAL_CHAMBER_STATE1_FILL_START; } } else { // Allow to proceed next state even though pressure is not stabilized. state = BAL_CHAMBER_STATE1_FILL_START; } } else { state = BAL_CHAMBER_STATE1_FILL_START; } // Trigger pressure drop alarm after first cycle of balancing chamber switching complete if ( FALSE == isFirstCycleBCSwitchingCompleted ) { isFirstCycleBCSwitchingCompleted = TRUE; } return state; } /*********************************************************************//** * @brief * The checkSpentFillComplete function checks the pressure difference for * spent side fill completion and adjusts the D48 pump speed to align the * fill timing close to the switching period time. * @details \b Inputs: balChamberExecState * @details \b Outputs: none * @param spentDialPressure to check the switching time for BC and set D48 speed based on it. * @return the current state of balancing chamber states. *************************************************************************/ static void checkSpentFillComplete( F32 spentDialPressure ) { F32 diffInSpentPressure = 0.0F; U32 absDiffSpentFillCount = 0; U32 adjustedSpeed = 0; U32 spentDialPumpSpeed = getD48PumpSpeedForBCFill(); U32 initialD48PumpSpeed = getCalculatedD48PumpSpeedForBCFill(); U32 minD48Speed = initialD48PumpSpeed - ( initialD48PumpSpeed * D48_SPEED_RANGE_LIMIT ); U32 maxD48Speed = initialD48PumpSpeed + ( initialD48PumpSpeed * D48_SPEED_RANGE_LIMIT ); U32 d48SpeedPostRangeCheck = 0; F32 qdMlpm = getTDDialysateFlowrate(); BOOL result = FALSE; F32 spentFillCompletePresPsig; // Diener 2000 pump: spent fill complete pressure use legacy 25 PSI. Other pump: depends on Qd. if ( TRUE == getTestConfigStatus( TEST_CONFIG_DD_ENABLE_DIENER_2000_PUMP ) ) { spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES; } else { if ( qdMlpm <= QD_THRESHOLD_LOW_MLPM ) { spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES_QD_LOW_PSIG; } else if ( qdMlpm <= QD_THRESHOLD_HIGH_MLPM ) { spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES_QD_MID_PSIG; } else { spentFillCompletePresPsig = SPENT_FILL_COMPLETE_PRES_QD_HIGH_PSIG; } } // Collect the next sample of spent pressure lastPrevSpentDialPressure = prevSpentDialPressure; prevSpentDialPressure = spentDialPressure; // find the pressure difference for pressure spike detection diffInSpentPressure = fabs(spentDialPressure - lastPrevSpentDialPressure); if ( ( spentDialPressure >= spentFillCompletePresPsig ) && ( isSpentFillComplete != TRUE ) ) { // Check spent fill time against the balancing chamber closing period diffSpentFillCompleteCount = balChamberValveClosePeriod - currentBalChamberFillCounter; absDiffSpentFillCount = abs(diffSpentFillCompleteCount); adjustedSpeed = ( ( (F32)absDiffSpentFillCount / (F32)balChamberValveClosePeriod ) * spentDialPumpSpeed ) * D48_SPEED_ADJUST_FACTOR; result = ( ( diffSpentFillCompleteCount <= 0 ) && ( diffSpentFillCompleteCount >= -2 ) ) ? TRUE : FALSE; //Not adjust the D48 pump speed if the fill counter is just 50ms difference from the closing period(considering only positive DEADBAND as negative deadband might indicate under fill of BC). if ( 0 == result ) { if ( diffSpentFillCompleteCount < SPENT_DIFF_COUNT_ZERO ) { // Increase the D48 pump speed spentDialPumpSpeed += adjustedSpeed; } else { //Decrease the D48 pump speed spentDialPumpSpeed -= adjustedSpeed; } d48SpeedPostRangeCheck = RANGE( spentDialPumpSpeed, minD48Speed, maxD48Speed ); // Update the D48 pump speed setD48PumpSpeedForBCFill( d48SpeedPostRangeCheck ); } //Update spent fill is complete isSpentFillComplete = TRUE; } } /*********************************************************************//** * @brief * The getCurrentBalancingChamberExecState function returns the current state * of the balancing chamber. * @details \b Inputs: balChamberExecState * @details \b Outputs: none * @return the current state of balancing chamber states. *************************************************************************/ BAL_CHAMBER_EXEC_STATE_T getCurrentBalancingChamberExecState( void ) { return balChamberExecState; } /*********************************************************************//** * @brief * The getBalancingChamberFillinProgressStatus function returns the current * balancing chamber fill in progress status. * @details \b Inputs: isBalChamberFillInProgress * @details \b Outputs: none * @return the current balancing chamber fill in progress. *************************************************************************/ BOOL getBalancingChamberFillinProgressStatus( void ) { return isBalChamberFillInProgress; } /*********************************************************************//** * @brief * The getBalChamberSwitchingFreq function gets the balancing chamber switching * frequency value. * @details \b Inputs: balChamberSwitchingFreq * @details \b Outputs: none * @return balancing chamber switching frequency *************************************************************************/ F32 getBalChamberSwitchingFreq( void ) { F32 result = balChamberSwitchingFreq.data; if ( OVERRIDE_KEY == balChamberSwitchingFreq.override ) { result = balChamberSwitchingFreq.ovData; } return result; } /*********************************************************************//** * @brief * The setBalChamberSwitchingOnlyStatus function sets the balancing chamber * switching only On/Off status. * @details \b Inputs: balanceChamberSwitchingOnly * @details \b Outputs: none * @param OnOff to set the BC switch status * @return none *************************************************************************/ void setBalChamberSwitchingOnlyStatus( BOOL OnOff ) { balanceChamberSwitchingOnly = OnOff; } /*********************************************************************//** * @brief * The getBalChamberSwitchingOnlyStatus function gets the balancing chamber * switching only status. * @details \b Inputs: balanceChamberSwitchingOnly * @details \b Outputs: none * @return balancing chamber switching only without any pressure validation * and dosing delivery *************************************************************************/ BOOL getBalChamberSwitchingOnlyStatus( void ) { return balanceChamberSwitchingOnly; } /*********************************************************************//** * @brief * The getBalChamberSwitchingPeriod function gets the balancing chamber * switching period. * @details \b Inputs: balChamberSwitchingPeriod * @details \b Outputs: none * @return balancing chamber switching period *************************************************************************/ U32 getBalChamberSwitchingPeriod( void ) { return balChamberSwitchingPeriod; } /*********************************************************************//** * @brief * The getBalChamberDataPublishInterval function gets the balancing chamber * data publish interval. * @details \b Inputs: balChamberDataPublishInterval * @details \b Outputs: none * @return the interval at balancing chamber data being published. *************************************************************************/ static U32 getBalChamberDataPublishInterval( void ) { U32 result = balChamberDataPublishInterval.data; if ( OVERRIDE_KEY == balChamberDataPublishInterval.override ) { result = balChamberDataPublishInterval.ovData; } return result; } /*********************************************************************//** * @brief * The publishBalChamberData function broadcasts the balancing chamber * execution data at defined interval. * @details \b Inputs: balChamberDataPublicationTimerCounter * @details \b Outputs: DD balancing chamber data broadcast message sent * @details \b Message \Sent: MSG_ID_DD_BAL_CHAMBER_DATA to publish the balancing * chamber data. * @return none *************************************************************************/ static void publishBalChamberData( void ) { if ( ++balChamberDataPublicationTimerCounter >= getBalChamberDataPublishInterval() ) { BAL_CHAMBER_DATA_T data; data.balChamberExecState = (U32)balChamberExecState; data.balChamberSWState = (U32)balChamberSWState; data.balChamberSWFreq = getBalChamberSwitchingFreq(); data.balChamberSwPeriod = getD48PumpSpeedForBCFill(); data.isBalChamberFillInProgress = isSpentFillComplete; data.currentBalChamberSwitchingCounter = currentBalChamberFillCounter; data.isPressureStabilizedDuringFill = isPressureStabilizedDuringFill; data.balChamberSWOnlyState = balanceChamberSwitchingOnly; broadcastData( MSG_ID_DD_BAL_CHAMBER_DATA, COMM_BUFFER_OUT_CAN_DD_BROADCAST, (U08*)&data, sizeof( BAL_CHAMBER_DATA_T ) ); balChamberDataPublicationTimerCounter = 0; } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testDDBalChamberDataPublishIntervalOverride function overrides the * DD balancing chamber data publish interval. * @details \b Inputs: balChamberDataPublishInterval * @details \b Outputs: balChamberDataPublishInterval * @param Override message from Dialin which includes the interval * (in ms) to override the DD balancing chamber data publish interval to. * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testDDBalChamberDataPublishIntervalOverride( MESSAGE_T *message ) { BOOL result = u32BroadcastIntervalOverride( message, &balChamberDataPublishInterval, TASK_GENERAL_INTERVAL ); return result; } /*********************************************************************//** * @brief * The testBalChamberSwFreqOverride function sets the override value * of the balancing chamber switching frequency. * @details Inputs: balChamberSwitchingFreq * @details Outputs: balChamberSwitchingFreq * @param message Override message from Dialin which includes the override * value to override the balancing chamber switching frequency. * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testBalChamberSwFreqOverride( MESSAGE_T *message ) { BOOL result = f32Override( message, &balChamberSwitchingFreq ); return result; } /*********************************************************************//** * @brief * The testAcidDoseVolumeOverride function sets the override value * of the acid concentrate dosing volume. * @details Inputs: acidDoseVolume * @details Outputs: acidDoseVolume * @param message Override message from Dialin which includes the override * value to override the acid concentrate dosing volume. * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testAcidDoseVolumeOverride( MESSAGE_T *message ) { BOOL result = f32Override( message, &acidDoseVolume ); return result; } /*********************************************************************//** * @brief * The testBicarbDoseVolumeOverride function sets the override value * of the bicarb concentrate dosing volume. * @details Inputs: bicarbDoseVolume * @details Outputs: bicarbDoseVolume * @param message Override message from Dialin which includes the override * value to override the bicarb concentrate dosing volume. * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testBicarbDoseVolumeOverride( MESSAGE_T *message ) { BOOL result = f32Override( message, &bicarbDoseVolume ); return result; } /*********************************************************************//** * @brief * The testBCSwitchOnlyStartStopOverride function starts/stops balancing * chamber switching only without dosing delivery and pressure check condition. * @details \b Inputs: tester logged in * @details \b Outputs: tdDialysateFlowrate,balanceChamberSwitchingOnly * pendingBalanceChamberSwOnlyRequest * @param message set message from Dialin which includes the balancing chamber * to start/stop switching and update switching rate based on the flow rate. * @return TRUE if set request is successful, FALSE if not *************************************************************************/ BOOL testBCSwitchOnlyStartStopOverride( MESSAGE_T *message ) { BOOL result = FALSE; // Verify tester has logged in with TD if ( TRUE == isTestingActivated() ) { // Verify payload length is valid if ( sizeof( BC_SWITCHING_ONLY_START_CMD_PAYLOAD_T ) == message->hdr.payloadLen ) { BC_SWITCHING_ONLY_START_CMD_PAYLOAD_T payload; memcpy( &payload, message->payload, sizeof(BC_SWITCHING_ONLY_START_CMD_PAYLOAD_T) ); // Handle start command if ( ( TRUE == payload.startStop ) && ( ( payload.flowrate >= MIN_DIALYSATE_FLOW_RATE ) && ( payload.flowrate <= MAX_DIALYSATE_FLOW_RATE ) ) ) { // First set BC switching frequency based on the dialysate flow rate setTDDialysateFlowrate( payload.flowrate ); // update switching rate transitionToBalChamberFill(); // then set BC switching only flag to ignore pressure check and dosing. setBalChamberSwitchingOnlyStatus( TRUE ); // Now, if the current operating mode is standby mode idle state, execute the balancing chamber switching only control result = requestBCSwitchingOnlyStart(); } //Handle stop command if ( FALSE == payload.startStop ) { //Reset the flag setBalChamberSwitchingOnlyStatus( FALSE ); //Stop the BC switching execution only control requestBCSwitchingOnlyStop(); result = TRUE; } } } return result; } /**@}*/