/************************************************************************** * * Copyright (c) 2024-2024 Diality Inc. - All Rights Reserved. * * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * @file AirTrap.c * * @author (last) Sean * @date (last) 03-Oct-2024 * * @author (original) Sean * @date (original) 03-Oct-2024 * ***************************************************************************/ #include "AirPump.h" #include "AirTrap.h" #include "AlarmMgmtTD.h" #include "LevelSensors.h" #include "Messaging.h" #include "OperationModes.h" #include "PersistentAlarm.h" #include "Pressures.h" #include "TaskGeneral.h" #include "Timers.h" #include "Utilities.h" #include "Valve2Way.h" /** * @addtogroup AirTrap * @{ */ // ********** private definitions ********** #define AIR_TRAP_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Interval (ms/task time) at which the air trap data is published on the CAN bus. #define AIR_TRAP_FILL_TIMEOUT_MS ( 10 * MS_PER_SECOND ) ///< Air trap fill timeout period (in ms). #define AIR_PUMP_ON_DELAY_TIME_MS ( 10 * MS_PER_SECOND ) ///< Delay between air pump On (in ms). #define AIR_PUMP_ON_STOP_TIME_MIN_MS 200 ///< Stop air Pump time. #define AIR_PUMP_ON_ERROR_MAX_CNT 6 ///< Maximum number of air pump on events within time window before alarm triggered. Do not exceed MAX_TIME_WINDOWED_COUNT. #define AIR_PUMP_ON_ERROR_TIME_WIN_MS ( 60 * MS_PER_SECOND ) ///< Time window for Air Pump on count error. #define AIR_TRAP_ILLEGAL_LEVELS_TIMEOUT_MS ( 2 * MS_PER_SECOND ) ///< Air trap illegal values timeout (in ms) #define VENOUS_LINE_VOLUME_ML ( 200.0F ) ///< Volume (in mL) of venous portion of blood circuit line. TODO - get actual volume from Systems. #define DATA_PUBLISH_COUNTER_START_COUNT 7 ///< Data publish counter start count. /// Air pump on delay after fill adjustment static const U32 AIR_PUMP_ON_DELAY_ADJUST_AFTER_FILL = ( AIR_PUMP_ON_DELAY_TIME_MS - ( 1 * MS_PER_SECOND ) ); /// Defined states for the air trap controller state machine. typedef enum AirTrap_States { AIR_TRAP_INIT_STATE = 0, ///< Initialization state AIR_TRAP_MANUAL_CONTROL_STATE, ///< Manually control air trap valve state AIR_TRAP_VALVE_CLOSED_STATE, ///< Valve closed state - until air detected at lower level AIR_TRAP_VALVE_OPEN_STATE, ///< Valve open state - until fluid detected at upper level NUM_OF_AIR_TRAP_STATES ///< Number of air trap controller states } AIR_TRAP_STATE_T; // ********** private data ********** static AIR_TRAP_STATE_T airTrapControllerState; ///< Current state of air trap controller state machine. static U32 airTrapDataPublicationTimerCounter; ///< Used to schedule air trap data publication to CAN bus. static OVERRIDE_U32_T airTrapDataPublishInterval; ///< Interval (in ms) at which to publish air trap data to CAN bus. static BOOL pendingStartAirTrapController = FALSE; ///< Flag indicates an air trap controller start request is pending. static BOOL pendingStopAirTrapController = FALSE; ///< Flag indicates an air trap controller stop request is pending. static U32 fillStartTime; ///< Time stamp for start of air trap fill. static U32 airPumpOnDelayStartTime; ///< Air pump On start time. static U32 stopAirPumpStartTime; ///< Stop air pump start time. static BOOL airTrapValveOpenAtStartOfTreatement; ///< To Keep the fluid level high close to Air trap upper level during start of treatment // ********** private function prototypes ********** static AIR_TRAP_STATE_T handleAirTrapManualControlState( void ); static AIR_TRAP_STATE_T handleAirTrapValveClosedState( void ); static AIR_TRAP_STATE_T handleAirTrapValveOpenState( void ); static void publishAirTrapData( void ); /*********************************************************************//** * @brief * The initAirTrap function initializes the air trap controller unit. * @details \b Inputs: none * @details \b Outputs: Air trap controller unit initialized * @return none *************************************************************************/ void initAirTrap(void) { // Initialize level sensors and valve drivers initLevelSensors(); init2WayValves(); // Initialize controller variables resetAirTrap(); airTrapDataPublicationTimerCounter = DATA_PUBLISH_COUNTER_START_COUNT; airTrapDataPublishInterval.data = AIR_TRAP_DATA_PUB_INTERVAL; airTrapDataPublishInterval.ovData = AIR_TRAP_DATA_PUB_INTERVAL; airTrapDataPublishInterval.ovInitData = AIR_TRAP_DATA_PUB_INTERVAL; airTrapDataPublishInterval.override = OVERRIDE_RESET; fillStartTime = 0; airPumpOnDelayStartTime = 0; stopAirPumpStartTime = 0; airTrapValveOpenAtStartOfTreatement = FALSE; } /*********************************************************************//** * @brief * The resetAirTrap function resets certain parts of the air trap module * between treatments. * @details \b Inputs: none * @details \b Outputs: Air Trap controller reset. * @return none *************************************************************************/ void resetAirTrap( void ) { airTrapControllerState = AIR_TRAP_INIT_STATE; pendingStartAirTrapController = FALSE; pendingStopAirTrapController = FALSE; } /*********************************************************************//** * @brief * The startAirTrapControl function requests a start to air trap control. * @details \b Inputs: airTrapControllerState * @details \b Outputs: pendingStartAirTrapController * @return none *************************************************************************/ void startAirTrapControl( void ) { if ( FALSE == isAirTrapControlling() ) { pendingStartAirTrapController = TRUE; } } /*********************************************************************//** * @brief * The endAirTrapControl function requests a stop to air trap control. * @details \b Message \b Sent: MSG_ID_TD_EVENT if air trap control ended. * @details \b Inputs: airTrapControllerState * @details \b Outputs: pendingStopAirTrapController * @return none *************************************************************************/ void endAirTrapControl( void ) { if ( TRUE == isAirTrapControlling() ) { pendingStopAirTrapController = TRUE; set2WayValveState( H13, STATE_CLOSED ); // Always exit air trap valve control w/ valve closed. SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_AIR_TRAP_FILL, STATE_CLOSED, 0 ); signalLowVenousPressureCheck(); // Venous pressure check should continue even after ending auto air trap control } } /*********************************************************************//** * @brief * The isAirTrapControlling function determines whether the air trap is * currently controlling. * @details \b Inputs: airTrapControllerState * @details \b Outputs: none * @return TRUE if air trap is currently controlling, FALSE if not. *************************************************************************/ BOOL isAirTrapControlling( void ) { BOOL result = FALSE; if ( airTrapControllerState >= AIR_TRAP_VALVE_CLOSED_STATE ) { result = TRUE; } return result; } /*********************************************************************//** * @brief * The execAirTrapMonitor function executes the air trap monitor. * @details \b Alarm: ALARM_ID_TD_AIR_TRAP_ILLEGAL_LEVELS if upper level * sensor detects liquid while the lower level sensor detects air. * @details \b Inputs: air trap level sensor readings * @details \b Outputs: none * @return none *************************************************************************/ void execAirTrapMonitor( void ) { BOOL isAirTrapLevelsValid = FALSE; AIR_TRAP_LEVELS_T lowerAirTrap, upperAirTrap; // Update level sensors from FPGA readLevelSensors(); // Check for illegal levels alarm if ( TRUE ) // TODO - need a way to determine whether a blood set is installed - if not installed, we would not trigger illegal levels alarm { // If the pump track on open, zero the persistent counter to not check the air trap illegal level alarm checkPersistentAlarm( ALARM_ID_TD_AIR_TRAP_ILLEGAL_LEVELS, FALSE, 0.0, 0.0 ); } else { lowerAirTrap = getLevelSensorState( H17 ); upperAirTrap = getLevelSensorState( H16 ); isAirTrapLevelsValid = ( ( ( AIR_TRAP_LEVEL_AIR == lowerAirTrap ) && ( AIR_TRAP_LEVEL_FLUID == upperAirTrap ) ) ? TRUE : FALSE ); #ifndef _RELEASE_ // if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_ILLEGAL_AIR_TRAP_ALARM ) != SW_CONFIG_ENABLE_VALUE ) #endif { checkPersistentAlarm( ALARM_ID_TD_AIR_TRAP_ILLEGAL_LEVELS, isAirTrapLevelsValid, lowerAirTrap, upperAirTrap ); } } } /*********************************************************************//** * @brief * The execAirTrapMonitorTreatment function executes the air trap monitor * for treatment mode. * @details \b Alarm: ALARM_ID_TD_AIR_TRAP_FILL_DURING_TREATMENT if air trap * fill exceeds maximum allowed time to complete. * @details \b Inputs: airTrapControllerState, fillStartTime * @details \b Outputs: none * @return none *************************************************************************/ void execAirTrapMonitorTreatment( void ) { // Check air trap fill timeout during treatment if ( AIR_TRAP_VALVE_OPEN_STATE == airTrapControllerState ) { if ( TRUE == didTimeout( fillStartTime, AIR_TRAP_FILL_TIMEOUT_MS ) ) { #ifndef _RELEASE_ // if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_AIR_TRAP_LEVELING_ALARM ) != SW_CONFIG_ENABLE_VALUE ) #endif { activateAlarmNoData( ALARM_ID_TD_AIR_TRAP_FILL_DURING_TREATMENT ); } } } } /*********************************************************************//** * @brief * The execAirTrapController function executes the air trap control state machine. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if current air trap control * state is invalid. * @details \b Message \b Sent: MSG_ID_TD_EVENT if air trap valve closed due to fault. * @details \b Inputs: airTrapControllerState * @details \b Outputs: airTrapControllerState * @return none *************************************************************************/ void execAirTrapController( void ) { // If we have faulted, close valve and go to manual control if ( MODE_FAUL == getCurrentOperationMode() ) { airTrapControllerState = AIR_TRAP_MANUAL_CONTROL_STATE; set2WayValveState( H13, STATE_CLOSED ); SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_AIR_TRAP_FILL, STATE_CLOSED, 0 ); pendingStartAirTrapController = FALSE; } // Execute air trap state machine switch( airTrapControllerState ) { case AIR_TRAP_INIT_STATE: airTrapControllerState = AIR_TRAP_MANUAL_CONTROL_STATE; break; case AIR_TRAP_MANUAL_CONTROL_STATE: airTrapControllerState = handleAirTrapManualControlState(); break; case AIR_TRAP_VALVE_CLOSED_STATE: airTrapControllerState = handleAirTrapValveClosedState(); break; case AIR_TRAP_VALVE_OPEN_STATE: airTrapControllerState = handleAirTrapValveOpenState(); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, (U32)SW_FAULT_ID_AIR_TRAP_INVALID_STATE, (U32)airTrapControllerState ) airTrapControllerState = AIR_TRAP_INIT_STATE; break; } // Publish air trap data if due publishAirTrapData(); } /*********************************************************************//** * @brief * The handleAirTrapManualControlState function handles the manual control * state of the air trap. * @details \b Inputs: pendingStartAirTrapController * @details \b Outputs: none * @return next state *************************************************************************/ static AIR_TRAP_STATE_T handleAirTrapManualControlState( void ) { AIR_TRAP_STATE_T result = AIR_TRAP_MANUAL_CONTROL_STATE; // Transition to valve control states when requested if ( TRUE == pendingStartAirTrapController ) { pendingStartAirTrapController = FALSE; set2WayValveState( H13, STATE_CLOSED ); SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_AIR_TRAP_FILL, STATE_CLOSED, 0 ); result = AIR_TRAP_VALVE_CLOSED_STATE; } return result; } /*********************************************************************//** * @brief * The handleAirTrapValveClosedState function handles the valve closed state * of the air trap. * @details \b Message \b Sent: MSG_ID_TD_EVENT if opening air trap valve. * @details \b Inputs: pendingStopAirTrapController * @details \b Outputs: none * @return next state *************************************************************************/ static AIR_TRAP_STATE_T handleAirTrapValveClosedState( void ) { AIR_TRAP_STATE_T result = AIR_TRAP_VALVE_CLOSED_STATE; // Air pump stop time based on the blood flow rate S32 qB = 0; // TODO abs( getTargetBloodFlowRate() ); U32 qBx = 0; // TODO ( 0 == qB ? MIN_SET_BLOOD_FLOW_RATE : (U32)qB ); U32 airPumpStopTime = 0; // TODO AIR_PUMP_ON_STOP_TIME_MIN_MS + ( ( qBx - MIN_SET_BLOOD_FLOW_RATE ) / 2 ); // Transition to manual valve control state when requested if ( TRUE == pendingStopAirTrapController ) { pendingStopAirTrapController = FALSE; result = AIR_TRAP_MANUAL_CONTROL_STATE; if ( AIR_PUMP_STATE_ON == getAirPumpState() ) { setAirPumpState( AIR_PUMP_STATE_OFF ); } } // Open valve once at start of treatment (blood priming) else if ( TRUE == airTrapValveOpenAtStartOfTreatement ) { airTrapValveOpenAtStartOfTreatement = FALSE; if ( AIR_PUMP_STATE_ON == getAirPumpState() ) { setAirPumpState( AIR_PUMP_STATE_OFF ); } set2WayValveState( H13, STATE_OPEN ); fillStartTime = getMSTimerCount(); SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_AIR_TRAP_FILL, STATE_OPEN, 0 ); result = AIR_TRAP_VALVE_OPEN_STATE; } // Turn air pump off after defined time or air detected at upper level else if ( ( AIR_PUMP_STATE_ON == getAirPumpState() ) && ( ( TRUE == didTimeout( stopAirPumpStartTime, airPumpStopTime ) ) || ( ( AIR_TRAP_LEVEL_AIR == getRawLevelSensorState( H16 ) ) && ( AIR_TRAP_LEVEL_FLUID == getLevelSensorState( H17 ) ) ) ) ) { setAirPumpState( AIR_PUMP_STATE_OFF ); airPumpOnDelayStartTime = getMSTimerCount(); } // Turn on air pump if fluid reaches upper level. else if ( AIR_TRAP_LEVEL_FLUID == getLevelSensorState( H16 ) ) { if ( ( AIR_PUMP_STATE_OFF == getAirPumpState() ) && ( TRUE == didTimeout( airPumpOnDelayStartTime, AIR_PUMP_ON_DELAY_TIME_MS ) ) ) { setAirPumpState( AIR_PUMP_STATE_ON ); stopAirPumpStartTime = getMSTimerCount(); signalInitiatePressureStabilization( USE_SHORT_STABILIZATION_PERIOD ); } } // Transition to open valve state when air detected at lower level else if ( ( AIR_TRAP_LEVEL_AIR == getLevelSensorState( H16 ) ) && ( AIR_TRAP_LEVEL_AIR == getLevelSensorState( H17 ) ) ) { if ( AIR_PUMP_STATE_ON == getAirPumpState() ) { setAirPumpState( AIR_PUMP_STATE_OFF ); } set2WayValveState( H13, STATE_OPEN ); fillStartTime = getMSTimerCount(); SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_AIR_TRAP_FILL, STATE_OPEN, 0 ); result = AIR_TRAP_VALVE_OPEN_STATE; } return result; } /*********************************************************************//** * @brief * The handleAirTrapValveOpenState function handles the valve open state of * the air trap. * @details \b Message \b Sent: MSG_ID_TD_EVENT if closing air trap valve * @details \b Inputs: pendingStopAirTrapController * @details \b Outputs: none * @return next state *************************************************************************/ static AIR_TRAP_STATE_T handleAirTrapValveOpenState( void ) { AIR_TRAP_STATE_T result = AIR_TRAP_VALVE_OPEN_STATE; // Transition to manual valve control state when requested if ( TRUE == pendingStopAirTrapController ) { pendingStopAirTrapController = FALSE; result = AIR_TRAP_MANUAL_CONTROL_STATE; } // Transition to closed valve state when fluid detected at upper level else if ( AIR_TRAP_LEVEL_FLUID == getRawLevelSensorState( H16 ) ) { set2WayValveState( H13, STATE_CLOSED ); airPumpOnDelayStartTime = u32DiffWithWrap( AIR_PUMP_ON_DELAY_ADJUST_AFTER_FILL, getMSTimerCount() ); signalLowVenousPressureCheck(); SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_AIR_TRAP_FILL, STATE_CLOSED, 0 ); result = AIR_TRAP_VALVE_CLOSED_STATE; } return result; } /*********************************************************************//** * @brief * The publishAirTrapData function constructs and sends the air trap data * broadcast message. * @details \b Message \b Sent: MSG_ID_TD_AIR_TRAP_DATA * @details \b Inputs: airTrapDataPublicationTimerCounter, airTrapControllerState, TODO valvestate * @details \b Outputs: airTrapDataPublicationTimerCounter * @return none *************************************************************************/ static void publishAirTrapData( void ) { // Publish air trap data on interval if ( ++airTrapDataPublicationTimerCounter >= getU32OverrideValue( &airTrapDataPublishInterval ) ) { AIR_TRAP_PAYLOAD_T data; data.h17State = getLevelSensorState( H17 ); data.h16State = getLevelSensorState( H16 ); data.h17RawState = getRawLevelSensorState( H17 ); data.h16RawState = getRawLevelSensorState( H16 ); data.h13State = get2WayValveState( H13 ); data.controlling = isAirTrapControlling(); broadcastData( MSG_ID_TD_AIR_TRAP_DATA, COMM_BUFFER_OUT_CAN_TD_BROADCAST, (U08*)&data, sizeof( AIR_TRAP_PAYLOAD_T ) ); airTrapDataPublicationTimerCounter = 0; } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testAirTrapDataPublishIntervalOverride function overrides the interval * at which the TD air trap data is published. * @details \b Inputs: none * @details \b Outputs: airTrapDataPublishInterval * @param message Override message from Dialin which includes the interval * (in ms) to override the air trap broadcast interval to. * @return TRUE if override request is successful, FALSE if not *************************************************************************/ BOOL testAirTrapDataPublishIntervalOverride( MESSAGE_T *message ) { BOOL result = u32BroadcastIntervalOverride( message, &airTrapDataPublishInterval, TASK_GENERAL_INTERVAL ); return result; } /**@}*/