#include "blk_box_drivers/inputs.hpp" #include "bottom_half.h" #include "pins.h" #include "driver/i2c.h" #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "esp_err.h" static const char *TAG = "INPUTS"; static TaskHandle_t expander_task_handle = NULL; static i2c_master_dev_handle_t expander_i2c_dev_handle; const static uint8_t REG_WHOAMI = 0x01; const static uint8_t REG_SW_VERSION = 0x02; const static uint8_t REG_EVENT_QUEUE_POP = 0x10; const static uint8_t REG_EVENT_QUEUE_LEN = 0x11; const static uint8_t REG_STATE_BUTTONS = 0x20; const static uint8_t REG_STATE_SWITCHES = 0x21; const static uint8_t REG_STATE_KEYPAD = 0x22; const static uint8_t REG_STATE_TOUCH = 0x23; const static uint8_t REG_STATE_RFID = 0x24; const static uint8_t REG_STATE_HALL = 0x25; const static uint8_t REG_STATE_CLOSE = 0x26; const static uint8_t REG_RESET = 0x30; const static uint8_t REG_HALL_SENSITIVITY = 0x31; const static uint8_t REG_CLOSE_SENSITIVITY = 0x32; const static uint8_t REG_SWITCH_TOUCH_EVENT = 0x33; /// The global data for the expander peripheral. class ExpanderPeripheral { // TODO: change these to private // or even make this class hidden public: SemaphoreHandle_t state_mutex; InputsState state; // channels QueueHandle_t button_press_events; QueueHandle_t button_release_events; QueueHandle_t switch_flip_events; QueueHandle_t switch_touch_events; QueueHandle_t touch_events; QueueHandle_t keypad_press_events; QueueHandle_t keypad_release_events; }; ExpanderPeripheral expander_peripheral_singleton; // forward declarations static void get_events(); static void handle_event(uint8_t event); static void handle_button_switch_event(uint8_t event); static void handle_keypad_event(uint8_t event); static void handle_touch_event(uint8_t event); static void handle_rfid_event(uint8_t event); static void handle_close_hal_event(uint8_t event); static void expander_task(void *arg); // ISR handler static void IRAM_ATTR expander_isr_handler(void *arg) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (expander_task_handle != NULL) { vTaskNotifyGiveFromISR(expander_task_handle, &xHigherPriorityTaskWoken); } if (xHigherPriorityTaskWoken == pdTRUE) { portYIELD_FROM_ISR(); } } void init_expander() { ESP_LOGI(TAG, "Initializing expander..."); i2c_device_config_t dev_config = { .dev_addr_length = I2C_ADDR_BIT_LEN_7, .device_address = EXPANDER_I2C_ADDR, .scl_speed_hz = EXPANDER_I2C_SPEED, .scl_wait_us = 0, // default .flags = { .disable_ack_check = 0, } }; // setup interrupt on BOTTOM_PIN_INTERUPT gpio_config_t io_conf = { .pin_bit_mask = (1ULL << BOTTOM_PIN_INTERUPT), .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_NEGEDGE }; ESP_ERROR_CHECK(gpio_config(&io_conf)); // Install ISR service (only call once in your program) ESP_ERROR_CHECK(gpio_install_isr_service(0)); // Attach the ISR to the expander pin ESP_ERROR_CHECK(gpio_isr_handler_add(BOTTOM_PIN_INTERUPT, expander_isr_handler, NULL)); // verify the expander connection status by reading the WHOAMI register uint8_t read_buf[2] = {0}; i2c_master_write_read_device(I2C_NUM_0, BOTTOM_I2C_ADDR, ®_WHOAMI, 1, read_buf, 1, 1000); if (read_buf[0] != EXPANDER_WHOAMI_VALUE) { ESP_LOGE(TAG, "WHOAMI mismatch, expected 0x%02X, got 0x%02X", EXPANDER_WHOAMI_VALUE, read_buf[0]); return; } ESP_LOGD(TAG, "Expander WHOAMI check passed"); ESP_ERROR_CHECK(i2c_master_transmit_receive(expander_i2c_dev_handle, ®_SW_VERSION, 1, read_buf, 2, EXPANDER_TIMEOUT_MS)); // init the peripheral struct expander_peripheral_singleton.state_mutex = xSemaphoreCreateMutex(); expander_peripheral_singleton.button_press_events= xQueueCreate(EXPANDER_EVENT_QUEUE_SIZE, sizeof(Button)); expander_peripheral_singleton.button_release_events= xQueueCreate(EXPANDER_EVENT_QUEUE_SIZE, sizeof(Button)); expander_peripheral_singleton.switch_flip_events= xQueueCreate(EXPANDER_EVENT_QUEUE_SIZE, sizeof(SwitchFlip)); expander_peripheral_singleton.switch_touch_events= xQueueCreate(EXPANDER_EVENT_QUEUE_SIZE, sizeof(SwitchTouch)); expander_peripheral_singleton.touch_events= xQueueCreate(EXPANDER_EVENT_QUEUE_SIZE, sizeof(TouchedReleased)); expander_peripheral_singleton.keypad_press_events= xQueueCreate(EXPANDER_KEYPAD_QUEUE_SIZE, sizeof(KeypadKey)); expander_peripheral_singleton.keypad_release_events= xQueueCreate(EXPANDER_KEYPAD_QUEUE_SIZE, sizeof(KeypadKey)); ESP_LOGI(TAG, "Expander initialized! SW version: v%d.%d", read_buf[0], read_buf[1]); // Create the expander background worker task BaseType_t task_created = xTaskCreate( expander_task, "expander_task", 4096, NULL, tskIDLE_PRIORITY + 1, &expander_task_handle ); if (task_created != pdPASS) { ESP_LOGE(TAG, "Failed to create expander task"); } } static void expander_task(void *arg) { (void)arg; while (true) { get_events(); // Wait for interrupt notification (signal is sent when INT falls) ulTaskNotifyTake(pdTRUE, portMAX_DELAY); } } static void get_events() { uint8_t recv; while (gpio_get_level(PIN_EXPANDER_INT) == 0) { ESP_ERROR_CHECK(i2c_master_transmit_receive(expander_i2c_dev_handle, ®_EVENT_QUEUE_POP, 1, &recv, 1, EXPANDER_TIMEOUT_MS)); handle_event(recv); } } static void handle_event(uint8_t event) { const uint8_t BUTTON_SWITCH = 0b000; const uint8_t KEYPAD = 0b001; const uint8_t TOUCH = 0b010; const uint8_t RFID = 0b011; ESP_LOGD(TAG, "Expander event: 0b%08b (0x%02X)", event, event); if (event == 0) { ESP_LOGE(TAG, "We read from event queue while it was empty!"); return; } uint8_t type_bits = event >> 5; switch (type_bits) { case BUTTON_SWITCH: handle_button_switch_event(event); break; case KEYPAD: handle_keypad_event(event); break; case TOUCH: handle_touch_event(event); break; case RFID: handle_rfid_event(event); break; default: handle_close_hal_event(event); break; } } static void handle_button_switch_event(uint8_t event) { const uint8_t PRESSED_NOT_RELEASED_BIT = 0b10000; const uint8_t SWITCH_NOT_BUTTON_BIT = 0b01000; const uint8_t SWITCH_UP_NOT_DOWN_BIT = 0b00100; const uint8_t NUMBER_MASK = 0b00011; bool pressed = (event & PRESSED_NOT_RELEASED_BIT) != 0; uint8_t number = event & NUMBER_MASK; if ((event & SWITCH_NOT_BUTTON_BIT) != 0) { // For now, we support two position switches by only looking at the switch up events bool switch_up = (event & SWITCH_UP_NOT_DOWN_BIT) != 0; if (!switch_up) { return; } Switch sw = static_cast(number); SwitchFlip sw_flip = SwitchFlip(sw, pressed); xQueueSendToBack(expander_peripheral_singleton.switch_flip_events, &sw_flip, 0); xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY); if (pressed) { // set expander_peripheral_singleton.state.switch_state |= 1 << number; } else { // clear expander_peripheral_singleton.state.switch_state &= ~(1 << number); } xSemaphoreGive(expander_peripheral_singleton.state_mutex); } else { // button Button button = static_cast