[gpio] Fix linker error when binary sensor only uses expander pins

This commit is contained in:
J. Nick Koston
2026-08-23 16:54:51 -05:00
parent 98f138f6b2
commit c35cbbc92b
9 changed files with 36 additions and 6 deletions
@@ -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))
@@ -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<uint8_t>(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<InternalGPIOPin *>(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; }
@@ -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
+1
View File
@@ -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
+7
View File
@@ -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}
@@ -1,5 +1,6 @@
substitutions:
binary_sensor_pin: GPIO2
binary_sensor_pin_2: GPIO10
output_pin: GPIO3
switch_pin: GPIO4
switch_pin_2: GPIO5
@@ -1,5 +1,6 @@
substitutions:
binary_sensor_pin: GPIO12
binary_sensor_pin_2: GPIO18
output_pin: GPIO13
switch_pin: GPIO14
switch_pin_2: GPIO15
@@ -1,5 +1,6 @@
substitutions:
binary_sensor_pin: GPIO0
binary_sensor_pin_2: GPIO4
output_pin: GPIO2
switch_pin: GPIO15
switch_pin_2: GPIO12
@@ -1,5 +1,6 @@
substitutions:
binary_sensor_pin: GPIO2
binary_sensor_pin_2: GPIO8
output_pin: GPIO3
switch_pin: GPIO4
switch_pin_2: GPIO5