Newer
Older
ESP32-RetroPlay / main / tasks / button_task.c
// button_task.c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_log.h"     // For ESP_LOG macros
#include "button_task.h" // For start_button_monitor_task declaration
#include "button_led_comm.h" // For LED_COMMAND_ON/OFF and send_led_command

static const char *TAG = "BUTTON_TASK";

#define BUTTON_GPIO_LEFT 37  // Original button for general action, now Left
#define BUTTON_GPIO_RIGHT 36 // New button for Right movement

/**
 * @brief Structure to pass multiple parameters to the FreeRTOS task
 */
typedef struct {
    QueueHandle_t game_input_queue;
    QueueHandle_t led_control_queue;
} button_task_params_t;

/**
 * @brief FreeRTOS task to monitor button states and send input signals to the game
 * and control signals to the LED task.
 *
 * This task configures both button GPIOs as inputs with internal pull-down
 * resistors. It continuously reads the buttons' states. It determines the
 * desired paddle movement (-1 for left, 1 for right, 0 for no movement)
 * and sends this value to the game's input queue every frame, ensuring
 * continuous movement while a button is held. It also toggles the LED
 * state on any button press (rising edge).
 *
 * @param pvParameters A pointer to a button_task_params_t structure containing
 * the game input queue handle and the LED control queue handle.
 */
static void button_monitor_task(void *pvParameters)
{
    button_task_params_t *params = (button_task_params_t *)pvParameters;
    QueueHandle_t game_input_queue = params->game_input_queue;
    QueueHandle_t led_control_queue = params->led_control_queue;

    // --- Button Configuration ---
    gpio_reset_pin(BUTTON_GPIO_LEFT);
    gpio_set_direction(BUTTON_GPIO_LEFT, GPIO_MODE_INPUT);
    gpio_set_pull_mode(BUTTON_GPIO_LEFT, GPIO_PULLDOWN_ONLY);

    gpio_reset_pin(BUTTON_GPIO_RIGHT);
    gpio_set_direction(BUTTON_GPIO_RIGHT, GPIO_MODE_INPUT);
    gpio_set_pull_mode(BUTTON_GPIO_RIGHT, GPIO_PULLDOWN_ONLY);

    int last_button_left_state = gpio_get_level(BUTTON_GPIO_LEFT);
    int last_button_right_state = gpio_get_level(BUTTON_GPIO_RIGHT);
    int current_button_left_state;
    int current_button_right_state;

    bool led_state = false; // Initial LED state for toggling

    ESP_LOGI(TAG, "Button Monitor Task started. Monitoring GPIO %d (Left) and %d (Right).", BUTTON_GPIO_LEFT, BUTTON_GPIO_RIGHT);

    while (1)
    {
        current_button_left_state = gpio_get_level(BUTTON_GPIO_LEFT);
        current_button_right_state = gpio_get_level(BUTTON_GPIO_RIGHT);

        int game_input_signal = 0; // Default: no movement

        // Determine continuous game input signal
        if (current_button_left_state == 1) { // Left button is currently pressed
            game_input_signal = -1; // Signal for left movement
        } else if (current_button_right_state == 1) { // Right button is currently pressed
            game_input_signal = 1; // Signal for right movement
        } else {
            game_input_signal = 0; // No button pressed, no movement
        }

        // Send game input signal to the queue (overwrite if full to ensure latest state)
        if (game_input_queue != NULL)
        {
            // Use xQueueOverwrite to ensure the latest input is always available
            if (xQueueOverwrite(game_input_queue, &game_input_signal) != pdTRUE)
            {
                ESP_LOGW(TAG, "Failed to overwrite game input signal to queue (should not happen with overwrite).");
            }
            // No success log here as it's continuous and would spam logs
        }
        else
        {
            ESP_LOGE(TAG, "Game input queue is NULL! Cannot send game input signal.");
        }

        // Toggle LED on any button press (rising edge detection for LED only)
        if ((current_button_left_state == 1 && last_button_left_state == 0) ||
            (current_button_right_state == 1 && last_button_right_state == 0))
        {
            ESP_LOGI(TAG, "Any Button PRESSED detected (rising edge).");
            if (led_control_queue != NULL) {
                led_state = !led_state; // Toggle LED state
                if (xQueueSend(led_control_queue, &led_state, 0) != pdTRUE) {
                    ESP_LOGW(TAG, "Failed to send LED control signal to queue (queue full?).");
                } else {
                    ESP_LOGI(TAG, "LED control signal SENT: %s", led_state ? "ON" : "OFF");
                }
            } else {
                ESP_LOGE(TAG, "LED control queue is NULL! Cannot send LED signal.");
            }
        }
        
        last_button_left_state = current_button_left_state;   // Update the last known state
        last_button_right_state = current_button_right_state; // Update the last known state

        vTaskDelay(pdMS_TO_TICKS(20)); // Check buttons every 20ms for smoother response
    }
    // If the task ever exits (which it shouldn't in an embedded loop), free allocated memory
    free(params);
    vTaskDelete(NULL);
}

/**
 * @brief Initializes and starts the button monitoring task.
 * @param game_input_queue Handle to the queue for sending game input signals.
 * @param led_control_queue Handle to the queue for sending LED control signals.
 * @return ESP_OK on success, ESP_FAIL on failure.
 */
esp_err_t start_button_monitor_task(QueueHandle_t game_input_queue, QueueHandle_t led_control_queue)
{
    // Allocate memory for task parameters
    button_task_params_t *params = (button_task_params_t *)malloc(sizeof(button_task_params_t));
    if (params == NULL) {
        ESP_LOGE(TAG, "Failed to allocate memory for button task parameters!");
        return ESP_FAIL;
    }
    params->game_input_queue = game_input_queue;
    params->led_control_queue = led_control_queue;

    // Create the button monitor task, passing the parameters struct
    if (xTaskCreate(button_monitor_task, "Button_Monitor_Task", configMINIMAL_STACK_SIZE * 3, (void*)params, 5, NULL) != pdPASS)
    {
        ESP_LOGE(TAG, "Failed to create Button Monitor task!");
        free(params); // Free allocated memory on failure
        return ESP_FAIL;
    }
    ESP_LOGI(TAG, "Button Monitor task created successfully!");
    return ESP_OK;
}