/************************************************************************** * * Copyright (c) 2024-2025 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 Buttons.c * * @author (last) Sean Nash * @date (last) 24-Jun-2025 * * @author (original) Sean Nash * @date (original) 19-Sep-2024 * ***************************************************************************/ #include "Buttons.h" #include "CpldInterface.h" #include "Messaging.h" //#include "NVDataMgmt.h" #include "OperationModes.h" #include "TaskPriority.h" #include "Timers.h" /** * @addtogroup Buttons * @{ */ // ********** private definitions ********** /// Enumeration of button self-test states. typedef enum Button_Self_Test_States { BUTTON_SELF_TEST_STATE_START = 0, ///< Button self-test start state BUTTON_SELF_TEST_STATE_IN_PROGRESS, ///< Button self-test in progress state BUTTON_SELF_TEST_STATE_COMPLETE, ///< Button self-test completed state NUM_OF_BUTTON_SELF_TEST_STATES ///< Number of button self-test states } BUTTON_SELF_TEST_STATE_T; /// Enumeration of hardware buttons. typedef enum Buttons { BUTTON_OFF = 0, ///< Off button BUTTON_STOP, ///< Stop button NUM_OF_BUTTONS ///< Number of hardware buttons } BUTTON_T; /// Enumeration of off button commands to UI. typedef enum OffButtonCmdsToUI { OFF_BUTTON_CMD_PROMPT_USER_TO_CONFIRM = 0, ///< Prompt user to confirm power off command OFF_BUTTON_CMD_CANCEL_USER_CONFIRM_PROMPT, ///< Cancel user confirm prompt command OFF_BUTTON_CMD_REJECT_USER_OFF_REQUEST, ///< Reject user off request command NUM_OF_OFF_BUTTON_CMDS ///< Number of off button commands to UI } OFF_BUTTON_CMD_T; /// Enumeration of off button responses from UI. typedef enum OffButtonRspsFromUI { OFF_BUTTON_RSP_USER_REQUESTS_POWER_OFF = 0, ///< User requests power off response OFF_BUTTON_RSP_USER_CONFIRMS_POWER_OFF, ///< User confirms power off response OFF_BUTTON_RSP_USER_REJECTS_POWER_OFF, ///< User rejects power off response NUM_OF_OFF_BUTTON_RSPS ///< Number of off button responses from UI } OFF_BUTTON_RSP_T; #define OFF_REQUEST_PULSE_COUNT 4 ///< Number of edges for power off sequence on power off output signal. #define OFF_REQUEST_PULSE_INTVL_MS 50 ///< Duration (in ms) of power off sequence steps. #define OFF_REQUEST_DELAY_TIME_MS 2000 ///< Duration (in ms) of delay before power off sequence is initiated to provide sub-systems time to wrap things up. #define STOP_BUTTON_PENDING_TIMEOUT_MS 500 ///< Timeout period (in ms) for stop button press to be consumed. #define STUCK_BUTTON_TIMEOUT_MS 1000 ///< Duration (in ms) that a button must be in pressed state before considering it stuck at power up. #define OFF_REQUEST_EXPIRATION_TIME_MS (1000 * 60) ///< Time (in ms) that the user is given to confirm a power off request before it expires. #define USER_CONFIRMED 1 ///< User response code to power off confirmation prompt that indicates confirmation. #define USER_REJECTED 0 ///< User response code to power off confirmation prompt that indicates rejection. // ********** private data ********** static OVERRIDE_U32_T dataOffButtonState; ///< Current off button state (overrideable). static BUTTON_STATE_T prevOffButtonState; ///< Previous state of off button. static BOOL offRequestAwaitingUserConfirmation = FALSE; ///< Flag indicates whether a power off request is pending user confirmation. static U32 offRequestPendingTimer; ///< Timer counter for pending power off request. static BOOL offButtonPressPending; ///< Flag indicates whether a confirmed power off request is pending execution. static U32 offRequestPulseCount; ///< Power off sequence step counter. static U32 offRequestPulseTimer; ///< Power off sequence step timer counter. static U32 offRequestDelayTimer; ///< Power off sequence delay timer counter. static OVERRIDE_U32_T dataStopButtonState; ///< Current stop button state (overrideable). static BUTTON_STATE_T prevStopButtonState; ///< Previous state of stop button. static BOOL stopButtonPressPending = FALSE; ///< Flag indicates a stop button press is pending consumption. static U32 stopButtonPendingTimer; ///< Timer counter for pending stop button press. static BUTTON_SELF_TEST_STATE_T buttonSelfTestState; ///< Current state of button self-test. static U32 buttonSelfTestTimerCount; ///< Timer counter for button self-test states. // ********** private function prototypes ********** static void handleOffButtonProcessing( void ); static void handleStopButtonProcessing( void ); static BOOL isCurrentOpModeOkToTurnOff( void ); /*********************************************************************//** * @brief * The initButtons function initializes the Buttons unit. * @details \b Inputs: none * @details \b Outputs: Buttons monitor unit initialized. * @return none *************************************************************************/ void initButtons( void ) { prevOffButtonState = BUTTON_STATE_RELEASED; offRequestAwaitingUserConfirmation = FALSE; offRequestPendingTimer = 0; offButtonPressPending = FALSE; offRequestPulseCount = 0; offRequestPulseTimer = 0; offRequestDelayTimer = 0; prevStopButtonState = BUTTON_STATE_RELEASED; stopButtonPressPending = FALSE; stopButtonPendingTimer = 0; buttonSelfTestState = BUTTON_SELF_TEST_STATE_START; buttonSelfTestTimerCount = 0; dataOffButtonState.data = BUTTON_STATE_RELEASED; dataOffButtonState.ovData = BUTTON_STATE_PRESSED; dataOffButtonState.ovInitData = BUTTON_STATE_PRESSED; dataOffButtonState.override = OVERRIDE_RESET; dataStopButtonState.data = BUTTON_STATE_RELEASED; dataStopButtonState.ovData = BUTTON_STATE_PRESSED; dataStopButtonState.ovInitData = BUTTON_STATE_PRESSED; dataStopButtonState.override = OVERRIDE_RESET; prevOffButtonState = BUTTON_STATE_RELEASED; prevStopButtonState = BUTTON_STATE_RELEASED; } /*********************************************************************//** * @brief * The execButtons function executes the Buttons monitor. * @details \b Inputs: none * @details \b Outputs: offButtonState, stopButtonState, prevOffButtonState, prevStopButtonState * @return none *************************************************************************/ void execButtons( void ) { #ifndef TEST_USE_OFF_AS_STOP_BUTTON PIN_SIGNAL_STATE_T off = getCPLDOffButton(); PIN_SIGNAL_STATE_T stop = getCPLDStopButton(); #else PIN_SIGNAL_STATE_T off = PIN_SIGNAL_LOW; PIN_SIGNAL_STATE_T stop = getCPLDOffButton(); #endif // Set current button states read from CPLD dataOffButtonState.data = ( off == PIN_SIGNAL_HIGH ? BUTTON_STATE_PRESSED : BUTTON_STATE_RELEASED ); dataStopButtonState.data = ( stop == PIN_SIGNAL_HIGH ? BUTTON_STATE_PRESSED : BUTTON_STATE_RELEASED ); // Handle button state transitions for stop button handleStopButtonProcessing(); // Handle button state transitions for off button handleOffButtonProcessing(); } /*********************************************************************//** * @brief * The isStopButtonPressed function determines whether the stop button has been * pressed. Once the stop button has transitioned from released to pressed, a * press for the stop button will be pending until this function is called. * @details \b Inputs: stopButtonPressPending * @details \b Outputs: stopButtonPressPending * @return true if the stop button is pressed, false if not *************************************************************************/ BOOL isStopButtonPressed( void ) { BOOL result = stopButtonPressPending; stopButtonPressPending = FALSE; return result; } /*********************************************************************//** * @brief * The getOffButtonState function determines whether the off button is * currently pressed. * @details \b Inputs: dataOffButtonState, prevOffButtonState * @details \b Outputs: none * @return BUTTON_STATE_PRESSED if off button pressed, BUTTON_STATE_RELEASED if not *************************************************************************/ BUTTON_STATE_T getOffButtonState( void ) { BUTTON_STATE_T result = (BUTTON_STATE_T)getU32OverrideValue( &dataOffButtonState ); return result; } /*********************************************************************//** * @brief * The getStopButtonState function determines whether the stop button is * currently pressed. * @details \b Inputs: dataStopButtonState, prevStopButtonState * @details \b Outputs: none * @return BUTTON_STATE_PRESSED if stop button pressed, BUTTON_STATE_RELEASED if not *************************************************************************/ BUTTON_STATE_T getStopButtonState( void ) { BUTTON_STATE_T result = (BUTTON_STATE_T)getU32OverrideValue( &dataStopButtonState ); return result; } /*********************************************************************//** * @brief * The execStuckButtonTest function executes the stuck button test. * @note This function should be called periodically until a pass or fail * result is returned. * @details \b Alarm: ALARM_ID_TD_STUCK_BUTTON_TEST_FAILED if test fails. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if test state is invalid. * @details \b Inputs: buttonSelfTestState, buttonSelfTestTimerCount * @details \b Outputs: buttonSelfTestState, buttonSelfTestTimerCount * @return Test status (in progress, passed, or failed) *************************************************************************/ SELF_TEST_STATUS_T execStuckButtonTest( void ) { SELF_TEST_STATUS_T result = SELF_TEST_STATUS_IN_PROGRESS; switch ( buttonSelfTestState ) { case BUTTON_SELF_TEST_STATE_START: buttonSelfTestState = BUTTON_SELF_TEST_STATE_IN_PROGRESS; buttonSelfTestTimerCount = getMSTimerCount(); // No break here so we pass through directly to in progress processing case BUTTON_SELF_TEST_STATE_IN_PROGRESS: if ( ( dataOffButtonState.data == BUTTON_STATE_RELEASED ) && ( dataStopButtonState.data == BUTTON_STATE_RELEASED ) ) { buttonSelfTestState = BUTTON_SELF_TEST_STATE_COMPLETE; result = SELF_TEST_STATUS_PASSED; } else if ( TRUE == didTimeout( buttonSelfTestTimerCount, STUCK_BUTTON_TIMEOUT_MS ) ) { U32 almData = ( dataStopButtonState.data == BUTTON_STATE_PRESSED ? BUTTON_STOP : BUTTON_OFF ); SET_ALARM_WITH_1_U32_DATA( ALARM_ID_TD_STUCK_BUTTON_TEST_FAILED, almData ) buttonSelfTestState = BUTTON_SELF_TEST_STATE_COMPLETE; result = SELF_TEST_STATUS_FAILED; } // Else just stay in progress and wait for next call break; case BUTTON_SELF_TEST_STATE_COMPLETE: // If we get called in this state, assume we are doing self-test again buttonSelfTestState = BUTTON_SELF_TEST_STATE_START; break; default: result = SELF_TEST_STATUS_FAILED; SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_BUTTONS_INVALID_SELF_TEST_STATE, buttonSelfTestState ) break; } return result; } /*********************************************************************//** * @brief * The resetStuckButtonPOSTState function resets the stuck button POST state. * @details \b Inputs: none * @details \b Outputs: buttonSelfTestState * @return none *************************************************************************/ void resetStuckButtonPOSTState( void ) { buttonSelfTestState = BUTTON_SELF_TEST_STATE_START; } /*********************************************************************//** * @brief * The userConfirmOffButton function handles user confirmation of the off * button. The off request will be initiated here if confirmed or cancelled * if rejected by user. * @details \b Inputs: current operation mode * @details \b Outputs: stopButtonPressPending * @param response 1 = confirmed, 0 = rejected * @return none *************************************************************************/ void userConfirmOffButton( U08 response ) { switch ( response ) { case OFF_BUTTON_RSP_USER_REQUESTS_POWER_OFF: // If we are in a mode that allows power off, set off pending flag and request user confirmation if ( TRUE == isCurrentOpModeOkToTurnOff() ) { offRequestAwaitingUserConfirmation = TRUE; offRequestPendingTimer = 0; #ifndef SIMULATE_UI sendOffButtonMsgToUI( OFF_BUTTON_CMD_PROMPT_USER_TO_CONFIRM ); #endif } else { // Send rejection response to power off request sendOffButtonMsgToUI( OFF_BUTTON_CMD_REJECT_USER_OFF_REQUEST ); } break; case OFF_BUTTON_RSP_USER_CONFIRMS_POWER_OFF: // Is an off request pending user confirmation? if ( TRUE == offRequestAwaitingUserConfirmation ) { // Reset off request pending flag offRequestAwaitingUserConfirmation = FALSE; // If we are in a mode that allows power off, initiate power off sequence if ( TRUE == isCurrentOpModeOkToTurnOff() ) { POWER_OFF_WARNING_DATA_T data; data.powerOffWarning = 0; broadcastData( MSG_ID_POWER_OFF_WARNING, COMM_BUFFER_OUT_CAN_TD_BROADCAST, (U08*)&data, sizeof( POWER_OFF_WARNING_DATA_T ) ); //signalPowerOffWarning(); // warn nvdata unit that power is going down offButtonPressPending = TRUE; offRequestPulseCount = OFF_REQUEST_PULSE_COUNT; offRequestPulseTimer = 0; } else { sendOffButtonMsgToUI( OFF_BUTTON_CMD_REJECT_USER_OFF_REQUEST ); } } else { sendOffButtonMsgToUI( OFF_BUTTON_CMD_REJECT_USER_OFF_REQUEST ); } break; case OFF_BUTTON_RSP_USER_REJECTS_POWER_OFF: // Is an off request pending user confirmation? if ( TRUE == offRequestAwaitingUserConfirmation ) { // Reset off request pending flag offRequestAwaitingUserConfirmation = FALSE; } break; default: // Ok - do nothing break; } // End switch } /*********************************************************************//** * @brief * The isCurrentOpModeOkToTurnOff function determines whether the system can * be turned off in current operation mode. * @details \b Inputs: Current operation mode. * @details \b Outputs: none * @return TRUE if can turn system off in current mode, FALSE if not *************************************************************************/ static BOOL isCurrentOpModeOkToTurnOff( void ) { TD_OP_MODE_T opMode = getCurrentOperationMode(); BOOL result = FALSE; if ( ( opMode == MODE_STAN ) || ( opMode == MODE_SERV ) || ( opMode == MODE_FAUL ) ) { result = TRUE; } return result; } /*********************************************************************//** * @brief * The initiatePowerOff function initiates a power off sequence. * @details \b Inputs: none * @details \b Outputs: offRequestDelayTimer, offRequestPulseTimer, offRequestPulseCount, offButtonPressPending * @return none *************************************************************************/ void initiatePowerOff( void ) { // Warn NV Data Mgr that power will be lost shortly // TODO signalPowerOffWarning(); // Mimic confirmed power off button state to initiate power off cycle offRequestDelayTimer = 0; offRequestPulseTimer = 0; offRequestPulseCount = OFF_REQUEST_PULSE_COUNT; offButtonPressPending = TRUE; } /*********************************************************************//** * @brief * The handleOffButtonProcessing function checks for and processes off button * activity. * @details \b Message \b Sent: TD_EVENT_BUTTON when off button state changes. * @details \b Message \b Sent: OFF_BUTTON_CMD_CANCEL_USER_CONFIRM_PROMPT when * pending off button expires. * @details \b Inputs: offButtonState, prevOffButtonState * @details \b Outputs: offButtonPressPending, offRequestPulseCount, offRequestPulseTimer * @return none *************************************************************************/ static void handleOffButtonProcessing( void ) { // Handle button state transitions for off button if ( getOffButtonState() != prevOffButtonState ) { if ( getOffButtonState() == BUTTON_STATE_PRESSED ) { // If off request in a valid mode, send to UI for user confirmation userConfirmOffButton( OFF_BUTTON_RSP_USER_REQUESTS_POWER_OFF ); // Log off button press SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_BUTTON, BUTTON_OFF, BUTTON_STATE_PRESSED ) } else { SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_BUTTON, BUTTON_OFF, BUTTON_STATE_RELEASED ) } prevOffButtonState = getOffButtonState(); } // If off request has not been confirmed by user before it expires, cancel it if ( TRUE == offRequestAwaitingUserConfirmation ) { offRequestPendingTimer += TASK_PRIORITY_INTERVAL; if ( offRequestPendingTimer >= OFF_REQUEST_EXPIRATION_TIME_MS ) { offRequestAwaitingUserConfirmation = FALSE; sendOffButtonMsgToUI( OFF_BUTTON_CMD_CANCEL_USER_CONFIRM_PROMPT ); } } // If user confirmed off button press, manage off request sequence if ( TRUE == offButtonPressPending ) { // Delay power off to provide sub-systems time to prepare for shutdown offRequestDelayTimer += TASK_PRIORITY_INTERVAL; if ( offRequestDelayTimer >= OFF_REQUEST_DELAY_TIME_MS ) { // Power off sequence is 4 50 ms toggles of the off request output signal offRequestPulseTimer += TASK_PRIORITY_INTERVAL; if ( offRequestPulseTimer >= OFF_REQUEST_PULSE_INTVL_MS ) { offRequestPulseTimer = 0; offRequestPulseCount--; if ( offRequestPulseCount == 0 ) { offButtonPressPending = false; } toggleCPLDOffRequest(); } } } } /*********************************************************************//** * @brief * The handleStopButtonProcessing function checks for and processes stop button * activity. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if a pending stop button is * expires. * @details \b Message \b Sent: TD_EVENT_BUTTON when stop button state changes. * @details \b Inputs: stopButtonState, prevStopButtonState * @details \b Outputs: stopButtonPressPending * @return none *************************************************************************/ static void handleStopButtonProcessing( void ) { // Handle button state transitions for stop button if ( getStopButtonState() != prevStopButtonState ) { if ( getStopButtonState() == BUTTON_STATE_PRESSED ) { stopButtonPressPending = TRUE; stopButtonPendingTimer = getMSTimerCount(); // Log stop button press SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_BUTTON, BUTTON_STOP, BUTTON_STATE_PRESSED ) } else { SEND_EVENT_WITH_2_U32_DATA( TD_EVENT_BUTTON, BUTTON_STOP, BUTTON_STATE_RELEASED ) } prevStopButtonState = getStopButtonState(); } // Handle when a stop button press is pending if ( TRUE == stopButtonPressPending ) { // If stop button not consumed within a reasonable time, s/w fault if ( TRUE == didTimeout( stopButtonPendingTimer, STOP_BUTTON_PENDING_TIMEOUT_MS ) ) { stopButtonPressPending = FALSE; #ifndef TEST_NO_STOP_CONSUME_CHECK SET_ALARM_WITH_1_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_BUTTONS_STOP_BUTTON_NOT_CONSUMED ) #endif } } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testOffButtonOverride function overrides the off button state. * @details \b Inputs: none * @details \b Outputs: dataOffButtonState * @param message Override message from Dialin which includes the state to * override the off button to. * @return TRUE if override request is successful, FALSE if not *************************************************************************/ BOOL testOffButtonOverride( MESSAGE_T *message ) { BOOL result = u32Override( message, &dataOffButtonState, 0, NUM_OF_BUTTON_STATES - 1 ); return result; } /*********************************************************************//** * @brief * The testStopButtonOverride function overrides the stop button state. * @details \b Inputs: none * @details \b Outputs: dataStopButtonState * @param message Override message from Dialin which includes the state to * override the stop button to. * @return TRUE if override request is successful, FALSE if not *************************************************************************/ BOOL testStopButtonOverride( MESSAGE_T *message ) { BOOL result = u32Override( message, &dataStopButtonState, 0, NUM_OF_BUTTON_STATES - 1 ); return result; } /**@}*/