/************************************************************************** * * Copyright (c) 2024-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 Valves.c * * @author (last) Sean Nash * @date (last) 04-May-2026 * * @author (original) Sean Nash * @date (original) 24-Oct-2024 * ***************************************************************************/ #include "AlarmMgmtTD.h" #include "CpldInterface.h" #include "FpgaTD.h" #include "Messaging.h" #include "OperationModes.h" #include "PersistentAlarm.h" #include "PinchValve.h" #include "TaskGeneral.h" #include "Timers.h" #include "Valves.h" /** * @addtogroup Valves * @{ */ // ********** private definitions ********** #define VALVES_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Valves data publish interval. #define DATA_PUBLISH_COUNTER_START_COUNT 13 ///< Valves data publish start counter. #define VALVE_TRANSITION_MIN_TGT_DELTA 10 ///< Minimum encoder position delta from target position to end transition state. #define VALVE_HOME_MIN_POS_CHG 3 ///< Minimum encoder position change to indicate a home operation is still moving toward edge. #define VALVE_HOME_BACK_OFF_EDGE 3 ///< Encoder counts to back off of detected edge position. #define MAX_HOME_FULL_TRAVEL_DIFF 10U ///< Maximum allowed difference in full travel encoder counts between expected and measured during home operation. #define VALVE_TRANSITION_TIMEOUT_MS ( 30 * MS_PER_SECOND ) ///< Valves transition time out in ms. #define HOMING_EDGE_DETECTION_TIMEOUT_MS ( 0.5 * MS_PER_SECOND ) ///< Valves homing edge detection timeout in milliseconds. #define VALVE_FORCE_HOME TRUE ///< Force valve to home even if already homed. #define ZERO_ENC_DEBOUNCE_THRESHOLD_CNT 5 ///< Valves zero encoder debounce threshold count. #define ZERO_ENC_DEBOUNCE_TIMEOUT_MS ( 0.25 * MS_PER_SECOND ) ///< Valves zero encoder debounce timeout in milliseconds. #define VALVE_OFFEST_FROM_EDG_CNT 296 ///< Valves offset from the edge. #define POS_C_FROM_ZERO_CNT VALVE_OFFEST_FROM_EDG_CNT ///< Position C from zero position in counts. #define POS_D_PARTIAL_CLOSE_FROM_ZERO_CNT ( POS_C_FROM_ZERO_CNT + 32 ) ///< Position D partial close from zero position in counts. /// Valve status structure typedef struct { VALVE_POSITION_T commandedPosition; ///< Valve commanded position enum. VALVE_POSITION_T currentPosition; ///< Valve current position enum. VALVE_POSITION_T pendingCommandedPosition; ///< Valve pending position enum. S16 priorEncPosition; ///< Valve prior position in encoder counts. BOOL hasTransitionBeenRequested; ///< Valve transition request flag. VALVE_STATE_T controlState; ///< Valve control state. U32 valveOpsStartTime; ///< Valve transition start time. // Homing variables BOOL hasHomingBeenRequested; ///< Valve homing request flag. BOOL hasValveBeenHomed; ///< Valve homing completed flag. BOOL homingOperationActive; } VALVE_STATUS_T; /// Payload record structure for pinch valve set position request typedef struct { U32 valve; ///< which pinch valve to set (0=H1, 1=H19) U32 pos; ///< 1=A, 2=B, 3=C } VALVE_PINCH_SET_CMD_PAYLOAD_T; /// Payload record structure for pinch valve home request typedef struct { U32 valve; ///< which pinch valve to home (0=H1, 1=H19) BOOL force; ///< Flag indicates whether home should be forced to happen even if already homed. BOOL tubing; ///< Flag indicates whether tubing will be present. } VALVE_PINCH_HOME_CMD_PAYLOAD_T; // ********** private data ********** static VALVE_STATUS_T currentValveStates[ NUM_OF_VALVES ]; ///< Current status of each valve. static U32 valvesDataPublicationTimerCounter; ///< Valves data broadcast timer counter. static OVERRIDE_U32_T valvesDataPublishInterval; ///< Valves data broadcast interval (in ms). // ********** private function prototypes ********** static VALVE_STATE_T handleValvesWait4PostState( VALVE_T valve ); static VALVE_STATE_T handleValvesNotHomedState( VALVE_T valve ); static VALVE_STATE_T handleValvesIdleState( VALVE_T valve ); static VALVE_STATE_T handleValvesTransitionState( VALVE_T valve ); static void publishValvesData( void ); /*********************************************************************//** * @brief * The initValves function initializes the valves controller unit. * @details \b Inputs: none * @details \b Outputs: Valves unit initialized * @return none *************************************************************************/ void initValves( void ) { VALVE_T valve; // Initialize Pinch Valve driver initPinchValveDriver(); // Initialize controller variables memset( ¤tValveStates[0], 0, sizeof( currentValveStates ) ); valvesDataPublicationTimerCounter = DATA_PUBLISH_COUNTER_START_COUNT; valvesDataPublishInterval.data = VALVES_DATA_PUB_INTERVAL; valvesDataPublishInterval.ovData = VALVES_DATA_PUB_INTERVAL; valvesDataPublishInterval.ovInitData = 0; valvesDataPublishInterval.override = OVERRIDE_RESET; for ( valve = FIRST_VALVE; valve < NUM_OF_VALVES; valve++ ) { currentValveStates[ valve ].commandedPosition = VALVE_POSITION_NOT_IN_POSITION; currentValveStates[ valve ].currentPosition = VALVE_POSITION_NOT_IN_POSITION; currentValveStates[ valve ].pendingCommandedPosition = VALVE_POSITION_NOT_IN_POSITION; currentValveStates[ valve ].hasTransitionBeenRequested = FALSE; currentValveStates[ valve ].hasHomingBeenRequested = FALSE; currentValveStates[ valve ].hasValveBeenHomed = FALSE; currentValveStates[ valve ].homingOperationActive = FALSE; currentValveStates[ valve ].controlState = VALVE_STATE_WAIT_FOR_POST; currentValveStates[ valve ].valveOpsStartTime = 0; } #ifdef ASN_DEMO currentValveStates[ H1_VALV ].positionsABC[ VALVE_POSITION_C_CLOSE ] = ROTARY_VALVE_MICROSTEP_FRACTION; currentValveStates[ H1_VALV ].positionsABC[ VALVE_POSITION_B_OPEN ] = 1728; currentValveStates[ H1_VALV ].positionsABC[ VALVE_POSITION_A_INSERT_EJECT ] = 880; currentValveStates[ H19_VALV ].positionsABC[ VALVE_POSITION_C_CLOSE ] = ROTARY_VALVE_MICROSTEP_FRACTION; currentValveStates[ H19_VALV ].positionsABC[ VALVE_POSITION_B_OPEN ] = 1728; currentValveStates[ H19_VALV ].positionsABC[ VALVE_POSITION_A_INSERT_EJECT ] = 880; #endif } /*********************************************************************//** * @brief * The homeValve function requests a valve homing operation on a given valve. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if given valve is invalid. * @details \b Inputs: currentValveStates[] * @details \b Outputs: currentValveStates[] * @param valve ID of valve that is set to be homed * @param force flag indicating whether homing should be forced even if already homed * @param cartridge flag indicating whether a cartridge may be present requiring a more lax homing * @return TRUE if the homing command accepted otherwise, FALSE *************************************************************************/ BOOL homeValve( VALVE_T valve, BOOL force, BOOL cartridge ) { BOOL result = FALSE; ( void )cartridge; if ( valve < NUM_OF_VALVES ) { if ( ( TRUE == force ) || ( FALSE == currentValveStates[ valve ].hasValveBeenHomed ) ) { if ( ( FALSE == isPinchValveBusy( valve ) ) && ( FALSE == currentValveStates[ valve ].hasHomingBeenRequested ) ) { currentValveStates[ valve ].hasHomingBeenRequested = TRUE; currentValveStates[ valve ].hasTransitionBeenRequested = FALSE; currentValveStates[ valve ].currentPosition = VALVE_POSITION_NOT_IN_POSITION; result = TRUE; } } else { result = TRUE; } } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_TD_VALVES_INVALID_VALVE8, (U32)valve ) } return result; } /*********************************************************************//** * @brief * The setValvePosition function sets the commanded position for a given valve. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if invalid valve or position given. * @details \b Inputs: none * @details \b Outputs: currentValveStates[] * @param valve ID of valve to command position for * @param position ID of position to command valve to * @return TRUE if command accepted, FALSE if not *************************************************************************/ BOOL setValvePosition( VALVE_T valve, VALVE_POSITION_T position ) { BOOL result = FALSE; if ( ( valve < NUM_OF_VALVES ) && ( position > VALVE_POSITION_NOT_IN_POSITION ) && ( position < NUM_OF_VALVE_POSITIONS ) ) { if ( ( TRUE == currentValveStates[ valve ].hasValveBeenHomed ) && ( FALSE == isPinchValveBusy( valve ) ) && ( FALSE == currentValveStates[ valve ].hasTransitionBeenRequested ) ) { currentValveStates[ valve ].pendingCommandedPosition = position; currentValveStates[ valve ].hasTransitionBeenRequested = TRUE; result = TRUE; } } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_TD_VALVES_INVALID_PARAM, ( (U32)valve << SHIFT_16_BITS_FOR_WORD_SHIFT ) | (U32)position ) } return result; } /*********************************************************************//** * @brief * The getValvePosition function returns the current position of a given valve. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if given valve is invalid. * @details \b Inputs: currentValveStates[] * @details \b Outputs: none * @param valve ID of valve to get current position of * @return Current position of the given valve *************************************************************************/ VALVE_POSITION_T getValvePosition( VALVE_T valve ) { VALVE_POSITION_T result = VALVE_POSITION_NOT_IN_POSITION; if ( valve < NUM_OF_VALVES ) { result = currentValveStates[ valve ].currentPosition; } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_TD_VALVES_INVALID_VALVE1, (U32)valve ) } return result; } /*********************************************************************//** * @brief * The getValveState function returns the current state of a given valve. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if given valve is invalid. * @details \b Inputs: currentValveStates[] * @details \b Outputs: none * @param valve ID of valve to get current state of * @return Current state of the given valve *************************************************************************/ VALVE_STATE_T getValveState( VALVE_T valve ) { VALVE_STATE_T result = VALVE_STATE_WAIT_FOR_POST; if ( valve < NUM_OF_VALVES ) { result = currentValveStates[ valve ].controlState; } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_TD_VALVES_INVALID_VALVE2, (U32)valve ) } return result; } /*********************************************************************//** * @brief * The execValvesController function executes the valves state machine. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if a valve control state is invalid. * @details \b Inputs: currentValveStates[] * @details \b Outputs: currentValveStates[] * @return none *************************************************************************/ void execValvesController( void ) { VALVE_T valve; // execPinchValveCommand(); // execPinchValveFunction(); for ( valve = FIRST_VALVE; valve < NUM_OF_VALVES; valve++ ) { // execute valve state machine switch ( currentValveStates[ valve ].controlState ) { case VALVE_STATE_WAIT_FOR_POST: currentValveStates[ valve ].controlState = handleValvesWait4PostState( valve ); break; case VALVE_STATE_HOMING_NOT_HOMED: currentValveStates[ valve ].controlState = handleValvesNotHomedState( valve ); break; case VALVE_STATE_IDLE: currentValveStates[ valve ].controlState = handleValvesIdleState( valve ); break; case VALVE_STATE_IN_TRANSITION: currentValveStates[ valve ].controlState = handleValvesTransitionState( valve ); break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_TD_VALVES_INVALID_STATE, (U32)currentValveStates[ valve ].controlState ) currentValveStates[ valve ].currentPosition = VALVE_POSITION_NOT_IN_POSITION; currentValveStates[ valve ].hasValveBeenHomed = FALSE; currentValveStates[ valve ].controlState = VALVE_STATE_HOMING_NOT_HOMED; break; } } publishValvesData(); } ///*********************************************************************//** // * @brief // * The execValvesSelfTest function executes the valves self-test. Calibration // * factors are loaded from non-volatile memory and CRC checked. // * @details \b Inputs: Calibration record stored in non-volatile memory. // * @details \b Outputs: valvesCalibrationRecord // * @return self-test result (pass/fail) // *************************************************************************/ //SELF_TEST_STATUS_T execValvesSelfTest( void ) //{ // BOOL calStatus = TRUE; /* TODO getNVRecord2Driver( GET_CAL_VALVES, (U08*)&valvesCalibrationRecord, sizeof( HD_VALVES_CAL_RECORD_T ), // NUM_OF_CAL_DATA_HD_VALVES, ALARM_ID_NO_ALARM );*/ // // if ( TRUE == calStatus ) // { // valvesSelfTestResult = SELF_TEST_STATUS_PASSED; // } // else // { // valvesSelfTestResult = SELF_TEST_STATUS_FAILED; // } // // return valvesSelfTestResult; //} // /*********************************************************************//** * @brief * The handleValvesWait4PostState function handles the Wait for POST state * of the state machine for a given valve. * @details \b Inputs: current operating mode * @details \b Outputs: none * @param valve ID of valve for which to handle the Wait for POST state * @return next state of the state machine for the given valve *************************************************************************/ static VALVE_STATE_T handleValvesWait4PostState( VALVE_T valve ) { VALVE_STATE_T nextState = VALVE_STATE_WAIT_FOR_POST; if ( valve < NUM_OF_VALVES ) { if ( MODE_INIT != getCurrentOperationMode() ) { nextState = VALVE_STATE_HOMING_NOT_HOMED; } } return nextState; } /*********************************************************************//** * @brief * The handleValvesNotHomedState function handles the Not Homed state * of the state machine for a given valve. * @details \b Inputs: currentValveStates[] * @details \b Outputs: currentValveStates[] * @param valve ID of valve for which to handle the Not Homed state * @return next state of the state machine for the given valve *************************************************************************/ static VALVE_STATE_T handleValvesNotHomedState( VALVE_T valve ) { VALVE_STATE_T nextState = VALVE_STATE_HOMING_NOT_HOMED; if ( TRUE == currentValveStates[ valve ].hasHomingBeenRequested ) { if ( TRUE == homePinchValve( valve ) ) { currentValveStates[ valve ].hasHomingBeenRequested = FALSE; currentValveStates[ valve ].hasValveBeenHomed = FALSE; currentValveStates[ valve ].homingOperationActive = TRUE; currentValveStates[ valve ].currentPosition = VALVE_POSITION_NOT_IN_POSITION; currentValveStates[ valve ].valveOpsStartTime = getMSTimerCount(); nextState = VALVE_STATE_IN_TRANSITION; } } return nextState; } /*********************************************************************//** * @brief * The handleValvesIdleState function handles the Idle state of the state * machine for a given valve. * @details \b Inputs: currentValveStates[] * @details \b Outputs: currentValveStates[], cmd to FPGA * @param valve ID of valve for which to handle the Idle state * @return next state of the state machine for the given valve *************************************************************************/ static VALVE_STATE_T handleValvesIdleState( VALVE_T valve ) { VALVE_STATE_T nextState = VALVE_STATE_IDLE; if ( TRUE == currentValveStates[ valve ].hasHomingBeenRequested ) { if ( TRUE == homePinchValve( valve ) ) { currentValveStates[ valve ].hasHomingBeenRequested = FALSE; currentValveStates[ valve ].hasValveBeenHomed = FALSE; currentValveStates[ valve ].homingOperationActive = TRUE; currentValveStates[ valve ].currentPosition = VALVE_POSITION_NOT_IN_POSITION; currentValveStates[ valve ].valveOpsStartTime = getMSTimerCount(); nextState = VALVE_STATE_IN_TRANSITION; } } else if ( TRUE == currentValveStates[ valve ].hasTransitionBeenRequested ) { if ( TRUE == movePinchValveToStoredPosition( valve, currentValveStates[ valve ].pendingCommandedPosition ) ) { currentValveStates[ valve ].hasTransitionBeenRequested = FALSE; currentValveStates[ valve ].commandedPosition = currentValveStates[ valve ].pendingCommandedPosition; currentValveStates[ valve ].homingOperationActive = FALSE; currentValveStates[ valve ].currentPosition = VALVE_POSITION_NOT_IN_POSITION; currentValveStates[ valve ].valveOpsStartTime = getMSTimerCount(); nextState = VALVE_STATE_IN_TRANSITION; } } return nextState; } /*********************************************************************//** * @brief * The handleValvesTransitionState function handles the Transition state * of the state machine for a given valve. * @details \b Inputs: currentValveStates[] * @details \b Outputs: currentValveStates[] * @param valve ID of valve for which to handle the Transition state * @return next state of the state machine for the given valve *************************************************************************/ static VALVE_STATE_T handleValvesTransitionState( VALVE_T valve ) { VALVE_STATE_T nextState = VALVE_STATE_IN_TRANSITION; if ( FALSE == isPinchValveBusy( valve ) ) { if ( TRUE == currentValveStates[ valve ].homingOperationActive ) { currentValveStates[ valve ].homingOperationActive = FALSE; if ( TRUE == isPinchValveHomed( valve ) ) { currentValveStates[ valve ].hasValveBeenHomed = TRUE; currentValveStates[ valve ].commandedPosition = VALVE_POSITION_A_INSERT_EJECT; currentValveStates[ valve ].currentPosition = VALVE_POSITION_A_INSERT_EJECT; nextState = VALVE_STATE_IDLE; } else { currentValveStates[ valve ].hasValveBeenHomed = FALSE; currentValveStates[ valve ].currentPosition = VALVE_POSITION_NOT_IN_POSITION; nextState = VALVE_STATE_HOMING_NOT_HOMED; } } else { currentValveStates[ valve ].currentPosition = currentValveStates[ valve ].commandedPosition; nextState = VALVE_STATE_IDLE; } } else if ( ( TRUE == didTimeout( currentValveStates[ valve ].valveOpsStartTime, VALVE_TRANSITION_TIMEOUT_MS ) ) && ( TRUE != getCPLDACPowerLossDetected() ) ) { currentValveStates[ valve ].currentPosition = VALVE_POSITION_NOT_IN_POSITION; SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_VALVE_TRANSITION_TIMEOUT, (U32)valve, (U32)getPinchValvePosition( valve ) ) if ( VALVE_POSITION_C_CLOSE == currentValveStates[ valve ].commandedPosition ) { activateSafetyShutdown(); } if ( TRUE == currentValveStates[ valve ].homingOperationActive ) { currentValveStates[ valve ].hasValveBeenHomed = FALSE; nextState = VALVE_STATE_HOMING_NOT_HOMED; } else { nextState = VALVE_STATE_IDLE; } } return nextState; } /*********************************************************************//** * @brief * The publishValvesData function constructs and sends the valves data * broadcast message. * @details \b Message \b Sent: MSG_ID_TD_VALVES_DATA * @details \b Inputs: valvesDataPublicationTimerCounter, valvesDataPublishInterval * @details \b Outputs: valvesDataPublicationTimerCounter * @return none *************************************************************************/ static void publishValvesData( void ) { if ( ++valvesDataPublicationTimerCounter >= getU32OverrideValue( &valvesDataPublishInterval ) ) { VALVE_T valve; for ( valve = FIRST_VALVE; valve < NUM_OF_VALVES; valve++ ) { TD_VALVE_DATA_T data; data.valveID = (U32)valve; data.state = (U32)currentValveStates[ valve ].controlState; data.currentPosID = (U32)currentValveStates[ valve ].currentPosition; data.cmdPos = currentValveStates[ valve ].commandedPosition; // data.posA = currentValveStates[ valve ].positionsABC[ VALVE_POSITION_A_INSERT_EJECT ]; // data.posB = currentValveStates[ valve ].positionsABC[ VALVE_POSITION_B_OPEN ]; // data.posC = currentValveStates[ valve ].positionsABC[ VALVE_POSITION_C_CLOSE ]; data.maxHomingPos = currentValveStates[ valve ].currentMaxEncPositionCnt; broadcastData( MSG_ID_TD_VALVES_DATA, COMM_BUFFER_OUT_CAN_TD_BROADCAST, (U08*)&data, sizeof( TD_VALVE_DATA_T ) ); } valvesDataPublicationTimerCounter = 0; } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testValvesDataPublishIntervalOverride function overrides the interval * at which the TD valves data is published. * @details \b Inputs: none * @details \b Outputs: valvesDataPublishInterval * @param message Override message from Dialin which includes the interval * (in ms) to override the valves broadcast interval to. * @return TRUE if override request is successful, FALSE if not *************************************************************************/ BOOL testValvesDataPublishIntervalOverride( MESSAGE_T *message ) { BOOL result = u32BroadcastIntervalOverride( message, &valvesDataPublishInterval, TASK_GENERAL_INTERVAL ); return result; } /*********************************************************************//** * @brief * The testValveSetABCCmdPosition function handles a Dialin command to set * a given valve to a given A/B/C position. * @details \b Inputs: none * @details \b Outputs: currentValveStates[] * @param message Override message from Dialin which includes the ID of the * valve and the position to command it to. * @return TRUE if override request is successful, FALSE if not *************************************************************************/ BOOL testValveSetABCCmdPosition( MESSAGE_T *message ) { BOOL result = FALSE; // Verify tester has logged in with TD if ( TRUE == isTestingActivated() ) { // Verify payload length is valid if ( sizeof( VALVE_PINCH_SET_CMD_PAYLOAD_T ) == message->hdr.payloadLen ) { VALVE_PINCH_SET_CMD_PAYLOAD_T payload; memcpy( &payload, message->payload, sizeof(VALVE_PINCH_SET_CMD_PAYLOAD_T) ); if ( ( (VALVE_T)payload.valve < NUM_OF_VALVES ) && ( (VALVE_POSITION_T)payload.pos < NUM_OF_VALVE_POSITIONS ) && ( (VALVE_POSITION_T)payload.pos < NUM_OF_VALVE_POSITIONS ) ) ) { result = setValvePosition( (VALVE_T)payload.valve, (VALVE_POSITION_T)payload.pos ); } } } return result; } /*********************************************************************//** * @brief * The testHomeValve function handles a Dialin command to home a given valve. * @details \b Inputs: none * @details \b Outputs: currentValveStates[] * @param message Override message from Dialin which includes the ID of the * valve to home and related flags. * @return TRUE if override request is successful, FALSE if not *************************************************************************/ BOOL testHomeValve( MESSAGE_T *message ) { BOOL result = FALSE; // Verify tester has logged in with TD if ( TRUE == isTestingActivated() ) { // Verify payload length is valid if ( sizeof( VALVE_PINCH_HOME_CMD_PAYLOAD_T ) == message->hdr.payloadLen ) { VALVE_PINCH_HOME_CMD_PAYLOAD_T payload; memcpy( &payload, message->payload, sizeof(VALVE_PINCH_HOME_CMD_PAYLOAD_T) ); if ( (VALVE_T)payload.valve < NUM_OF_VALVES ) { result = homeValve( (VALVE_T)payload.valve, payload.force, payload.tubing ); } } } return result; } /**@}*/