/************************************************************************** * * 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 Switches.c * * @author (last) N/A * @date (last) N/A * * @author (original) Steve Jarpe * @date (original) 22 May 2026 * ***************************************************************************/ #include "FpgaDD.h" #include "Messaging.h" //#include "NVDataMgmt.h" #include "Switches.h" #include "TaskGeneral.h" #include "Timers.h" #include "MsgDefs.h" /** * @addtogroup Switches * @{ */ // ********** private definitions ********** #define SWITCHES_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Interval (ms/task time) at which the switches data is published on the CAN bus. #define SWITCHES_DEBOUNCE_TIME_MS ( MS_PER_SECOND / 4 ) ///< Switches FPGA status check interval. #define DATA_PUBLISH_COUNTER_START_COUNT 90 ///< Data publish counter start count. #define MAX_CONNECTOR_NOT_IN_PLACE_ALARMS 3 ///< Maximum number of times the connector not in place alarm can occur. #define MAX_CAP_NOT_CLOSED_ALARMS 3 ///< Maximum number of times the cap not closed alarm can occur. #define MAX_CAP_NOT_CLOSED_INTERVAL ( ( 5 * SECONDS_PER_MINUTE *MS_PER_SECOND ) / TASK_GENERAL_INTERVAL ) ///< Maximum length of time the cap can be not closed in timer intervals // ********** private data ********** static U32 switchesDataPublicationCounter; ///< Switches data publication counter. static OVERRIDE_U32_T switchesDataPublishInterval; ///< Interval (in ms) at which to publish switches data to CAN bus. static OVERRIDE_U32_T switchesStatus[ NUM_OF_SWITCHES ]; ///< Debounced switch states. static U32 switchDebounceStartTimes[ NUM_OF_SWITCHES ]; ///< Switch debounce start times. static BOOL requireConnectorInPlace_CapNotParked; ///< Check for connector in place and cap parked condition static BOOL requireCapClosed_CapNotParked; ///< Check for cap closed and cap not parked condition static BOOL alarmConnectorNotInPlace_CapNotParked; ///< Is the ConnectorNotInPlace_CapNotParked alarm active? static BOOL alarmCapNotClosed_CapParked; ///< Is the CapClosed_CapNotParked alarm active? static U32 alarmConnectorNotInPlace_CapNotParked_count; ///< Is the ConnectorNotInPlace_CapNotParked alarm active? static U32 alarmCapNotClosed_CapParked_count; ///< Is the CapClosed_CapNotParked alarm active? static U32 alarmCapNotClosed_CapParked_seconds_counter; ///< Total duration of CapClosed_CapNotParked alarm. static U32 alarmCapNotClosed_CapParked_timer; ///< Timer for the duration of CapClosed_CapNotParked alarm. // ********** private function prototypes ********** static void publishSwitchesData( void ); static void handleSwitchAlarms( void ); /*********************************************************************//** * @brief * The initSwitches function initializes the switches unit. * @details \b Inputs: none * @details \b Outputs: Switches monitor unit initialized * @return none *************************************************************************/ void initSwitches( void ) { U32 i; switchesDataPublicationCounter = DATA_PUBLISH_COUNTER_START_COUNT; switchesDataPublishInterval.data = SWITCHES_DATA_PUB_INTERVAL; switchesDataPublishInterval.ovData = SWITCHES_DATA_PUB_INTERVAL; switchesDataPublishInterval.ovInitData = 0; switchesDataPublishInterval.override = OVERRIDE_RESET; requireConnectorInPlace_CapParked = FALSE; requireCapClosed_CapNotParked = FALSE; alarmConnectorNotInPlace_CapNotParked = FALSE; alarmCapNotClosed_CapParked = FALSE; alarmConnectorNotInPlace_CapNotParked_count = 0; alarmCapNotClosed_CapParked_count = 0; alarmCapNotClosed_CapParked_seconds_counter = 0; alarmCapNotClosed_CapParked_timer = 0; // Initialize all the switches for ( i = 0; i < NUM_OF_SWITCHES; i++ ) { switchesStatus[ i ].data = (U32)STATE_CLOSED; switchesStatus[ i ].ovData = (U32)STATE_CLOSED; switchesStatus[ i ].ovInitData = (U32)STATE_CLOSED; switchesStatus[ i ].override = OVERRIDE_RESET; switchDebounceStartTimes[ i ] = 0; } } /*********************************************************************//** * @brief * The execSwitches function executes the switches monitor executive. * Switch state changes are debounced for 250ms before formalizing them. * @details \b Inputs: FPGA switch states, switchesStatus[], switchDebounceStartTimes[] * @details \b Outputs: switchesStatus[], switchDebounceStartTimes[] * @return none *************************************************************************/ void execSwitches( void ) { U32 i; OPN_CLS_STATE_T currentSwitchStates[ NUM_OF_SWITCHES ]; // Get current state of each switch currentSwitchStates[ D101_SWCH ] = ( FALSE == getD101HDFCapConnectorStatus() ? STATE_OPEN : STATE_CLOSED ); currentSwitchStates[ D102_SWCH ] = ( FALSE == getD102HDFCapConnectorStatus() ? STATE_OPEN : STATE_CLOSED ); currentSwitchStates[ D103_SWCH ] = ( FALSE == getD103HDFCapParkedStatus() ? STATE_OPEN : STATE_CLOSED ); // Debounce each switch for ( i = 0; i < NUM_OF_SWITCHES; i++ ) { #ifndef _RELEASE_ // if ( SW_CONFIG_ENABLE_VALUE == getSoftwareConfigStatus( SW_CONFIG_DISABLE_SWITCHES_MONITOR ) ) // { // switchesStatus[ i ].status.data = STATE_CLOSED; // } // else #endif { // Check if the current switch status is not the same as the recorded data if ( (U32)currentSwitchStates[ i ] != switchesStatus[ i ].data ) { // If the debounce time is 0, start the timer if ( 0 == switchDebounceStartTimes[ i ] ) { switchDebounceStartTimes[ i ] = getMSTimerCount(); } // If the debounce time has been elapsed, update the switch status to the new status else if ( TRUE == didTimeout( switchDebounceStartTimes[ i ], SWITCHES_DEBOUNCE_TIME_MS ) ) { // If the bit is 0, the door switch is open, because it is normally open switch switchDebounceStartTimes[ i ] = 0; switchesStatus[ i ].data = currentSwitchStates[ i ]; } } else { switchDebounceStartTimes[ i ] = 0; } } } // Check for switch alarms handleSwitchAlarms(); // Handle publishing switches data publishSwitchesData(); } /*********************************************************************//** * @brief * The handleSwitchAlarms function checks for switch related alarms. * @details \b Alarm: ALARM_ID_HDF_DISPOSABLE_NOT_CONNECTED if HDF disposable * is not connected and cap is not parked. * @details \b Alarm: ALARM_ID_HDF_CAP_NOT_CLOSED if HDF cap is not * closed on the HDF port. Additionally the cap must not be detected in the park position. * @details \b Inputs: requireConnectorInPlace_CapParked, requireCapClosed_CapNotParked, switchesStatus * @details \b Outputs: alarms if required conditions are not met * @return none *************************************************************************/ static void handleSwitchAlarms( void ) { #ifndef _RELEASE_ // if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_SWITCHES_MONITOR ) != SW_CONFIG_ENABLE_VALUE ) #endif { // Check for connector not in place alarm during treatment // The optical switches should show that the connector is in place, and in addition // the cap should be in its parked position. if ( TRUE == requireConnectorInPlace_CapParked ) { if ( getSwitchState( D101_SWCH ) != STATE_CLOSED && getSwitchState( D102_SWCH ) != STATE_OPEN && getSwitchState( D103_SWCH ) != STATE_CLOSED &&) { activateAlarmNoData( ALARM_ID_HDF_DISPOSABLE_NOT_CONNECTED ); if ( FALSE == alarmConnectorNotInPlace_CapNotParked ) { if ( ++alarmConnectorNotInPlace_CapNotParked_count >= MAX_CONNECTOR_NOT_IN_PLACE_ALARMS ) { // This alarm can only occur a limited number of times before treatment is ended //END TREATMENT } } alarmConnectorNotInPlace_CapNotParked = TRUE; } else { clearAlarmCondition( ALARM_ID_HDF_DISPOSABLE_NOT_CONNECTED ); alarmConnectorNotInPlace_CapNotParked = FALSE; } } // Check for cap not closed alarm during disinfect // The cap should be over the connector port and in the closed position, and in addition // the cap parked indicator should not be activated. if ( TRUE == requireCapClosed_CapNotParked ) { if ( getSwitchState( D101_SWCH ) != STATE_OPEN && getSwitchState( D102_SWCH ) != STATE_CLOSED && getSwitchState( D103_SWCH ) != STATE_OPEN &&) { activateAlarmNoData( ALARM_ID_HDF_CAP_NOT_CLOSED ); if ( FALSE == alarmCapNotClosed_CapParked ) { if ( ++alarmCapNotClosed_CapParked_count >= MAX_CAP_NOT_CLOSED_ALARMS ) { // This alarm can only occur a limited number of times before disinfect must be restarted //REQUIRE DISINFECT } } alarmCapNotClosed_CapParked = TRUE; // Increment the timer for this alarm. This should happen at all times that the alarm is active if ( ++alarmCapNotClosed_CapParked_timer > MAX_CAP_NOT_CLOSED_INTERVAL ) { // This alarm can only be active for a limited amount of time before disinfect must be performed //REQUIRE DISINFECT } } else { clearAlarmCondition( ALARM_ID_HDF_CAP_NOT_CLOSED ); alarmCapNotClosed_CapParked = FALSE; } } } } /*********************************************************************//** * @brief * The getSwitchState function returns the state of a given switch. * @details \b Alarm: ALARM_ID_DD_SOFTWARE_FAULT if given switch ID is invalid. * @details \b Inputs: switchesStatus * @details \b Outputs: switchesStatus * @param switchId ID of switch to get state for * @return switch state of given switch *************************************************************************/ OPN_CLS_STATE_T getSwitchState( SWITCH_T switchId ) { U32 state = 0; if ( switchId < NUM_OF_SWITCHES ) { state = getU32OverrideValue( &switchesStatus[ switchId ] ); } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_DD_INVALID_SWITCH_ID, (U32)switchId ) } return (OPN_CLS_STATE_T)state; } /*********************************************************************//** * @brief * The requireCapParked_ConnectorInPlace function sets flags that determine whether * the HDF disposable must be connected and the HDF cap parked in current state. * Associated alarms will be triggered if switches not in required state. * @details \b Inputs: required or not required * @details \b Outputs: requireConnectorInPlace_CapParked * @param door is door required to be closed in current state? * @return none *************************************************************************/ // This alarm is active only during treatment and HDF is requested void requireConnectorInPlace_CapParked( BOOL required ) { requireConnectorInPlace_CapParked = required; } /*********************************************************************//** * @brief * The requireCapClosed_CapNotParked function sets flags that determine whether * the HDF disposable port must be blocked by the HDF cap. * Additionally the cap must not be in the parked position. * Associated alarms will be triggered if switches not in required state. * @details \b Inputs: required or not required * @details \b Outputs: requireCapClosed_CapNotParked * @return none *************************************************************************/ //! This alarm is meant to be active during all non-treatment modes. This activation mechanism // could be replaced by a mode check in handleSwitchAlarms to remove the burden // of calling this function multiple times void requireCapClosed_CapNotParked( BOOL required ) { requireCapClosed_CapNotParked = required; } /*********************************************************************//** * @brief * The publishSwitchesData function broadcasts the switches data at the * publication interval. * @details \b Message \b Sent: MSG_ID_DD_SWITCHES_DATA * @details \b Inputs: switchesDataPublicationCounter, switchesStatus[] * @details \b Outputs: switchesDataPublicationCounter * @return none *************************************************************************/ static void publishSwitchesData( void ) { if ( ++switchesDataPublicationCounter >= getU32OverrideValue( &switchesDataPublishInterval ) ) { SWITCHES_DATA_T data; switchesDataPublicationCounter = 0; data.d101Sw1 = (U32)getSwitchState( D101_SWCH ); data.d102Sw2 = (U32)getSwitchState( D102_SWCH ); data.d103CapParked = (U32)getSwitchState( D103_SWCH ); broadcastData( MSG_ID_DD_SWITCHES_DATA, COMM_BUFFER_OUT_CAN_DD_BROADCAST, (U08*)&data, sizeof( SWITCHES_DATA_T ) ); //broadcastData( MSG_ID_TD_BATTERY_DATA, COMM_BUFFER_OUT_CAN_DD_BROADCAST, (U08*)&data, sizeof( SWITCHES_DATA_T ) ); } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testSwitchesDataPublishIntervalOverride function overrides the interval * at which the DD switches data is published. * @details \b Inputs: none * @details \b Outputs: switchesDataPublishInterval * @param message Override message from Dialin which includes the interval * (in ms) to override the switches broadcast interval to. * @return TRUE if override request is successful, FALSE if not *************************************************************************/ BOOL testSwitchesDataPublishIntervalOverride( MESSAGE_T *message ) { BOOL result = u32BroadcastIntervalOverride( message, &switchesDataPublishInterval, TASK_GENERAL_INTERVAL ); return result; } /*********************************************************************//** * @brief * The testSwitchOverride function overrides the state for a given switch. * @details \b Inputs: none * @details \b Outputs: switchesStatus[] * @param message Override message from Dialin which includes an ID of * the switch to override and the state to override the switch to. * @return TRUE if override request is successful, FALSE if not *************************************************************************/ BOOL testSwitchOverride( MESSAGE_T *message ) { BOOL result = u32ArrayOverride( message, &switchesStatus[0], NUM_OF_SWITCHES - 1, 0, NUM_OF_OPN_CLS_STATES - 1 ); return result; } /**@}*/