From c35cbbc92bcc27eea8eb8ceab68ec3653918ea9b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 23 Aug 2026 16:54:51 -0500 Subject: [PATCH] [gpio] Fix linker error when binary sensor only uses expander pins --- .../components/gpio/binary_sensor/__init__.py | 1 + .../gpio/binary_sensor/gpio_binary_sensor.cpp | 20 ++++++++++++++----- .../gpio/binary_sensor/gpio_binary_sensor.h | 9 ++++++++- esphome/core/defines.h | 1 + tests/components/gpio/common.yaml | 7 +++++++ tests/components/gpio/test.esp32-c3-idf.yaml | 1 + tests/components/gpio/test.esp32-idf.yaml | 1 + tests/components/gpio/test.esp8266-ard.yaml | 1 + tests/components/gpio/test.rp2040-ard.yaml | 1 + 9 files changed, 36 insertions(+), 6 deletions(-) diff --git a/esphome/components/gpio/binary_sensor/__init__.py b/esphome/components/gpio/binary_sensor/__init__.py index 7cc16eb5b2..8a40e4e732 100644 --- a/esphome/components/gpio/binary_sensor/__init__.py +++ b/esphome/components/gpio/binary_sensor/__init__.py @@ -133,6 +133,7 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_pin(pin)) if config[CONF_USE_INTERRUPT]: + cg.add_define("USE_GPIO_BINARY_SENSOR_INTERRUPT") cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE])) else: cg.add(var.set_use_interrupt(False)) diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp index ff07d76901..5336c8dd83 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp @@ -7,6 +7,7 @@ namespace esphome::gpio { static const char *const TAG = "gpio.binary_sensor"; #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT // Interrupt type strings indexed by edge-triggered InterruptType values: // indices 1-3: RISING_EDGE, FALLING_EDGE, ANY_EDGE; other values (e.g. level-triggered) map to UNKNOWN (index 0). PROGMEM_STRING_TABLE(InterruptTypeStrings, "UNKNOWN", "RISING_EDGE", "FALLING_EDGE", "ANY_EDGE"); @@ -14,12 +15,14 @@ PROGMEM_STRING_TABLE(InterruptTypeStrings, "UNKNOWN", "RISING_EDGE", "FALLING_ED static const LogString *interrupt_type_to_string(gpio::InterruptType type) { return InterruptTypeStrings::get_log_str(static_cast(type), 0); } +#endif static const LogString *gpio_mode_to_string(bool use_interrupt) { return use_interrupt ? LOG_STR("interrupt") : LOG_STR("polling"); } #endif +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) { bool new_state = arg->isr_pin_.digital_read(); if (new_state != arg->state_) { @@ -43,28 +46,34 @@ void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, Component *component) { // Attach interrupt - from this point on, any changes will be caught by the interrupt pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, this->interrupt_type_); } +#endif // USE_GPIO_BINARY_SENSOR_INTERRUPT void GPIOBinarySensor::setup() { +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT if (this->store_.use_interrupt_) { auto *internal_pin = static_cast(this->pin_); this->store_.setup(internal_pin, this); this->publish_initial_state(this->store_.get_state()); - } else { - this->pin_->setup(); - this->publish_initial_state(this->pin_->digital_read()); + return; } +#endif + this->pin_->setup(); + this->publish_initial_state(this->pin_->digital_read()); } void GPIOBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this); LOG_PIN(" Pin: ", this->pin_); ESP_LOGCONFIG(TAG, " Mode: %s", LOG_STR_ARG(gpio_mode_to_string(this->store_.use_interrupt_))); +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT if (this->store_.use_interrupt_) { ESP_LOGCONFIG(TAG, " Interrupt Type: %s", LOG_STR_ARG(interrupt_type_to_string(this->store_.interrupt_type_))); } +#endif } void GPIOBinarySensor::loop() { +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT if (this->store_.use_interrupt_) { if (this->store_.is_changed()) { // Clear the flag immediately to minimize the window where we might miss changes @@ -78,9 +87,10 @@ void GPIOBinarySensor::loop() { // No changes, disable the loop until the next interrupt this->disable_loop(); } - } else { - this->publish_state(this->pin_->digital_read()); + return; } +#endif + this->publish_state(this->pin_->digital_read()); } float GPIOBinarySensor::get_setup_priority() const { return setup_priority::HARDWARE; } diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h index 100edb4cca..25f6a6c57a 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/components/binary_sensor/binary_sensor.h" @@ -10,6 +11,7 @@ namespace esphome::gpio { // Store class for ISR data and configuration (no vtables, ISR-safe) class GPIOBinarySensorStore { public: +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT void setup(InternalGPIOPin *pin, Component *component); static void gpio_intr(GPIOBinarySensorStore *arg); @@ -29,15 +31,18 @@ class GPIOBinarySensorStore { // Separate method to clear the flag this->changed_ = false; } +#endif protected: friend class GPIOBinarySensor; +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT ISRInternalGPIOPin isr_pin_; Component *component_{nullptr}; // Pointer to the component for enable_loop_soon_any_context() volatile bool state_{false}; volatile bool changed_{false}; - bool use_interrupt_{true}; gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE}; +#endif + bool use_interrupt_{true}; }; class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Component { @@ -47,7 +52,9 @@ class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Compon void set_pin(GPIOPin *pin) { this->pin_ = pin; } void set_use_interrupt(bool use_interrupt) { this->store_.use_interrupt_ = use_interrupt; } +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT void set_interrupt_type(gpio::InterruptType type) { this->store_.interrupt_type_ = type; } +#endif // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) /// Setup pin diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 20aca3776f..2f7b0b7527 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -72,6 +72,7 @@ #define USE_ESP32_IMPROV_STATE_CALLBACK #define USE_EVENT #define USE_FAN +#define USE_GPIO_BINARY_SENSOR_INTERRUPT #define USE_GPIO_SWITCH_INTERLOCK #define USE_GRAPH #define USE_GRAPHICAL_DISPLAY_MENU diff --git a/tests/components/gpio/common.yaml b/tests/components/gpio/common.yaml index b8e8fa81e4..bdd71a31e3 100644 --- a/tests/components/gpio/common.yaml +++ b/tests/components/gpio/common.yaml @@ -3,6 +3,13 @@ binary_sensor: pin: ${binary_sensor_pin} id: gpio_binary_sensor + # Polling sensor alongside an interrupt sensor: proves the mixed build where + # both code paths are compiled and selected per instance at runtime. + - platform: gpio + pin: ${binary_sensor_pin_2} + id: gpio_binary_sensor_polling + use_interrupt: false + output: - platform: gpio pin: ${output_pin} diff --git a/tests/components/gpio/test.esp32-c3-idf.yaml b/tests/components/gpio/test.esp32-c3-idf.yaml index e9071b4356..25b80f7dcb 100644 --- a/tests/components/gpio/test.esp32-c3-idf.yaml +++ b/tests/components/gpio/test.esp32-c3-idf.yaml @@ -1,5 +1,6 @@ substitutions: binary_sensor_pin: GPIO2 + binary_sensor_pin_2: GPIO10 output_pin: GPIO3 switch_pin: GPIO4 switch_pin_2: GPIO5 diff --git a/tests/components/gpio/test.esp32-idf.yaml b/tests/components/gpio/test.esp32-idf.yaml index 862aa533ea..e31d60cdda 100644 --- a/tests/components/gpio/test.esp32-idf.yaml +++ b/tests/components/gpio/test.esp32-idf.yaml @@ -1,5 +1,6 @@ substitutions: binary_sensor_pin: GPIO12 + binary_sensor_pin_2: GPIO18 output_pin: GPIO13 switch_pin: GPIO14 switch_pin_2: GPIO15 diff --git a/tests/components/gpio/test.esp8266-ard.yaml b/tests/components/gpio/test.esp8266-ard.yaml index e13b4520d1..3202da753a 100644 --- a/tests/components/gpio/test.esp8266-ard.yaml +++ b/tests/components/gpio/test.esp8266-ard.yaml @@ -1,5 +1,6 @@ substitutions: binary_sensor_pin: GPIO0 + binary_sensor_pin_2: GPIO4 output_pin: GPIO2 switch_pin: GPIO15 switch_pin_2: GPIO12 diff --git a/tests/components/gpio/test.rp2040-ard.yaml b/tests/components/gpio/test.rp2040-ard.yaml index e9071b4356..b3602e37b4 100644 --- a/tests/components/gpio/test.rp2040-ard.yaml +++ b/tests/components/gpio/test.rp2040-ard.yaml @@ -1,5 +1,6 @@ substitutions: binary_sensor_pin: GPIO2 + binary_sensor_pin_2: GPIO8 output_pin: GPIO3 switch_pin: GPIO4 switch_pin_2: GPIO5