Gate use_interrupt_ storage, add codegen define tests, drop compile fixture

This commit is contained in:
J. Nick Koston
2026-08-23 17:10:32 -05:00
parent c35cbbc92b
commit 7d0a65b90c
10 changed files with 112 additions and 15 deletions
@@ -15,12 +15,12 @@ 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
#endif
#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT
void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) {
@@ -64,11 +64,13 @@ void GPIOBinarySensor::setup() {
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
ESP_LOGCONFIG(TAG, " Mode: %s", LOG_STR_ARG(gpio_mode_to_string(this->store_.use_interrupt_)));
if (this->store_.use_interrupt_) {
ESP_LOGCONFIG(TAG, " Interrupt Type: %s", LOG_STR_ARG(interrupt_type_to_string(this->store_.interrupt_type_)));
}
#else
ESP_LOGCONFIG(TAG, " Mode: polling");
#endif
}
@@ -41,8 +41,8 @@ class GPIOBinarySensorStore {
volatile bool state_{false};
volatile bool changed_{false};
gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE};
#endif
bool use_interrupt_{true};
#endif
};
class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Component {
@@ -51,9 +51,13 @@ class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Compon
// Interrupts are only detached on reboot when memory is cleared anyway.
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_use_interrupt(bool use_interrupt) { this->store_.use_interrupt_ = use_interrupt; }
void set_interrupt_type(gpio::InterruptType type) { this->store_.interrupt_type_ = type; }
#else
// Polling-only build: codegen still emits set_use_interrupt(false) calls,
// so keep the setter as an inlined no-op instead of storing the flag.
void set_use_interrupt(bool /*use_interrupt*/) {}
#endif
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
@@ -3,10 +3,15 @@
from __future__ import annotations
from collections.abc import Callable
import logging
from pathlib import Path
import pytest
from esphome.core import CORE
INTERRUPT_DEFINE = "USE_GPIO_BINARY_SENSOR_INTERRUPT"
def test_gpio_binary_sensor_basic_setup(
generate_main: Callable[[str | Path], str],
@@ -69,3 +74,62 @@ def test_gpio_binary_sensor_explicit_polling_mode(
)
assert "bs_polling->set_use_interrupt(false);" in main_cpp
def test_gpio_binary_sensor_interrupt_emits_define(
generate_main: Callable[[str | Path], str],
) -> None:
"""
An interrupt-mode sensor must emit the define that compiles the ISR code,
since the platform ISR pin implementation is only built when needed
"""
generate_main("tests/component_tests/gpio/test_gpio_binary_sensor.yaml")
assert INTERRUPT_DEFINE in {d.name for d in CORE.defines}
def test_gpio_binary_sensor_polling_omits_define(
generate_main: Callable[[str | Path], str],
) -> None:
"""
A polling-only config must not emit the interrupt define, so the ISR code
(and its reference to ISRInternalGPIOPin) is compiled out
"""
generate_main("tests/component_tests/gpio/test_gpio_binary_sensor_polling.yaml")
assert INTERRUPT_DEFINE not in {d.name for d in CORE.defines}
def test_gpio_binary_sensor_mixed_modes_emit_define(
generate_main: Callable[[str | Path], str],
) -> None:
"""
With one interrupt and one polling sensor, the define is emitted and the
polling instance still opts out via its setter
"""
main_cpp = generate_main(
"tests/component_tests/gpio/test_gpio_binary_sensor_mixed.yaml"
)
assert INTERRUPT_DEFINE in {d.name for d in CORE.defines}
assert "bs_polling->set_use_interrupt(false);" in main_cpp
assert "bs_interrupt->set_use_interrupt" not in main_cpp
def test_gpio_binary_sensor_expander_pin_omits_define(
generate_main: Callable[[str | Path], str],
caplog: pytest.LogCaptureFixture,
) -> None:
"""
An expander pin can't use interrupts: final validation falls back to
polling and the interrupt define must not be emitted. This is the config
that fails to link if the ISR code is compiled without an internal pin
"""
with caplog.at_level(logging.INFO):
main_cpp = generate_main(
"tests/component_tests/gpio/test_gpio_binary_sensor_expander.yaml"
)
assert "bs_expander->set_use_interrupt(false);" in main_cpp
assert INTERRUPT_DEFINE not in {d.name for d in CORE.defines}
assert "falling back to polling mode" in caplog.text
@@ -0,0 +1,21 @@
esphome:
name: test
esp32:
board: esp32dev
i2c:
scl: 16
sda: 17
ch422g:
- id: ch422g_hub
binary_sensor:
- platform: gpio
name: "Expander Sensor"
id: bs_expander
pin:
ch422g: ch422g_hub
number: 1
mode: INPUT
@@ -0,0 +1,17 @@
esphome:
name: test
esp32:
board: esp32dev
binary_sensor:
- platform: gpio
pin: 5
name: "Interrupt Sensor"
id: bs_interrupt
- platform: gpio
pin: 4
name: "Polling Sensor"
id: bs_polling
use_interrupt: false
-7
View File
@@ -3,13 +3,6 @@ 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,6 +1,5 @@
substitutions:
binary_sensor_pin: GPIO2
binary_sensor_pin_2: GPIO10
output_pin: GPIO3
switch_pin: GPIO4
switch_pin_2: GPIO5
@@ -1,6 +1,5 @@
substitutions:
binary_sensor_pin: GPIO12
binary_sensor_pin_2: GPIO18
output_pin: GPIO13
switch_pin: GPIO14
switch_pin_2: GPIO15
@@ -1,6 +1,5 @@
substitutions:
binary_sensor_pin: GPIO0
binary_sensor_pin_2: GPIO4
output_pin: GPIO2
switch_pin: GPIO15
switch_pin_2: GPIO12
@@ -1,6 +1,5 @@
substitutions:
binary_sensor_pin: GPIO2
binary_sensor_pin_2: GPIO8
output_pin: GPIO3
switch_pin: GPIO4
switch_pin_2: GPIO5