From 59065c71a8f40c1ad93dff79f9a93f57e2928030 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 9 Apr 2026 13:28:24 -1000 Subject: [PATCH] [sx1509] Add interrupt pin support Add optional interrupt_pin configuration to eliminate I2C polling for GPIO reads. When configured, the component disables its loop and only reads the I2C bus when the interrupt fires, matching the pattern used by PCF8574, PCA9554, and other GPIO expanders. When keypad mode is active, the loop continues running for key scanning regardless of interrupt configuration. --- esphome/components/sx1509/__init__.py | 4 ++++ esphome/components/sx1509/sx1509.cpp | 19 +++++++++++++++++++ esphome/components/sx1509/sx1509.h | 4 ++++ tests/components/sx1509/common.yaml | 1 + tests/components/sx1509/test.esp32-idf.yaml | 3 +++ tests/components/sx1509/test.esp8266-ard.yaml | 3 +++ tests/components/sx1509/test.rp2040-ard.yaml | 3 +++ 7 files changed, 37 insertions(+) diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index b61b92fd1e..5b904bb1a6 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -5,6 +5,7 @@ import esphome.config_validation as cv from esphome.const import ( CONF_ID, CONF_INPUT, + CONF_INTERRUPT_PIN, CONF_INVERTED, CONF_MODE, CONF_NUMBER, @@ -75,6 +76,7 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(SX1509Component), cv.Optional(CONF_KEYPAD): cv.Schema(KEYPAD_SCHEMA), + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, } ) .extend(cv.COMPONENT_SCHEMA) @@ -86,6 +88,8 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) + if interrupt_pin := config.get(CONF_INTERRUPT_PIN): + cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) if conf := config.get(CONF_KEYPAD): cg.add(var.set_rows_cols(conf[CONF_KEY_ROWS], conf[CONF_KEY_COLUMNS])) if ( diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index 1cdae76eaf..0739fa1ef9 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -28,10 +28,23 @@ void SX1509Component::setup() { delayMicroseconds(500); if (this->has_keypad_) this->setup_keypad_(); + + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->setup(); + this->interrupt_pin_->attach_interrupt(&SX1509Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE); + this->set_invalidate_on_read_(false); + } + // Disable loop until an input pin is configured via pin_mode() + // or keypad is active. For interrupt-driven mode, loop is re-enabled by the ISR. + if (!this->has_keypad_) { + this->disable_loop(); + } } +void IRAM_ATTR SX1509Component::gpio_intr(SX1509Component *arg) { arg->enable_loop_soon_any_context(); } void SX1509Component::dump_config() { ESP_LOGCONFIG(TAG, "SX1509:"); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); if (this->is_failed()) { ESP_LOGE(TAG, "Setting up SX1509 failed!"); } @@ -41,6 +54,9 @@ void SX1509Component::dump_config() { void SX1509Component::loop() { // Reset cache at the start of each loop this->reset_pin_cache_(); + if (this->interrupt_pin_ != nullptr && !this->has_keypad_) { + this->disable_loop(); + } if (this->has_keypad_) { if (millis() - this->last_loop_timestamp_ < min_loop_period_) @@ -169,6 +185,9 @@ void SX1509Component::pin_mode(uint8_t pin, gpio::Flags flags) { // Set direction to input this->ddr_mask_ |= (1 << pin); this->write_byte_16(REG_DIR_B, this->ddr_mask_); + if (this->interrupt_pin_ == nullptr) { + this->enable_loop(); + } } } diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index f98fc0a44f..964a02dd52 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -46,6 +46,7 @@ class SX1509Component : public Component, uint16_t read_key_data(); void set_pin_value(uint8_t pin, uint8_t i_on) { this->write_byte(REG_I_ON[pin], i_on); }; void pin_mode(uint8_t pin, gpio::Flags flags); + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } uint32_t get_clock() { return this->clk_x_; }; void set_rows_cols(uint8_t rows, uint8_t cols) { this->rows_ = rows; @@ -63,6 +64,8 @@ class SX1509Component : public Component, void setup_led_driver(uint8_t pin); protected: + static void IRAM_ATTR gpio_intr(SX1509Component *arg); + // Virtual methods from CachedGpioExpander bool digital_read_hw(uint8_t pin) override; bool digital_read_cache(uint8_t pin) override; @@ -85,6 +88,7 @@ class SX1509Component : public Component, std::vector keypad_binary_sensors_; std::vector key_triggers_; + InternalGPIOPin *interrupt_pin_{nullptr}; uint32_t last_loop_timestamp_ = 0; const uint32_t min_loop_period_ = 15; // ms diff --git a/tests/components/sx1509/common.yaml b/tests/components/sx1509/common.yaml index cf7e234f09..7bcd565864 100644 --- a/tests/components/sx1509/common.yaml +++ b/tests/components/sx1509/common.yaml @@ -2,6 +2,7 @@ sx1509: - id: sx1509_hub i2c_id: i2c_bus address: 0x3E + interrupt_pin: ${interrupt_pin} keypad: key_rows: 2 key_columns: 2 diff --git a/tests/components/sx1509/test.esp32-idf.yaml b/tests/components/sx1509/test.esp32-idf.yaml index b47e39c389..8c3b341dce 100644 --- a/tests/components/sx1509/test.esp32-idf.yaml +++ b/tests/components/sx1509/test.esp32-idf.yaml @@ -1,3 +1,6 @@ +substitutions: + interrupt_pin: GPIO15 + packages: i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml diff --git a/tests/components/sx1509/test.esp8266-ard.yaml b/tests/components/sx1509/test.esp8266-ard.yaml index 4a98b9388a..69b243bfd8 100644 --- a/tests/components/sx1509/test.esp8266-ard.yaml +++ b/tests/components/sx1509/test.esp8266-ard.yaml @@ -1,3 +1,6 @@ +substitutions: + interrupt_pin: GPIO15 + packages: i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml diff --git a/tests/components/sx1509/test.rp2040-ard.yaml b/tests/components/sx1509/test.rp2040-ard.yaml index 319a7c71a6..b8ad1e4792 100644 --- a/tests/components/sx1509/test.rp2040-ard.yaml +++ b/tests/components/sx1509/test.rp2040-ard.yaml @@ -1,3 +1,6 @@ +substitutions: + interrupt_pin: GPIO2 + packages: i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml