[gpio] Keep each interlock group in one shared flash table (#20238)

This commit is contained in:
J. Nick Koston
2026-10-12 13:34:44 -10:00
committed by GitHub
parent 6c86a8903f
commit cce435a31e
5 changed files with 73 additions and 12 deletions
+12 -6
View File
@@ -36,11 +36,17 @@ async def to_code(config: ConfigType) -> None:
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
if CONF_INTERLOCK in config:
if (interlock := config.get(CONF_INTERLOCK)) is not None:
cg.add_define("USE_GPIO_SWITCH_INTERLOCK")
interlock = []
for it in config[CONF_INTERLOCK]:
lock = await cg.get_variable(it)
interlock.append(lock)
cg.add(var.set_interlock(interlock))
locks = [await cg.get_variable(it) for it in interlock]
# The group's table lists this switch too, so every member shares one table.
group = sorted({str(s): s for s in (var, *locks)}.values(), key=str)
if len(group) > 1:
table = cg.shared_progmem_array(
"gpio_interlock",
switch.Switch.operator("ptr"),
cg.ArrayInitializer(*group),
constexpr=False,
)
cg.add(var.set_interlock(table, len(group)))
cg.add(var.set_interlock_wait_time(config[CONF_INTERLOCK_WAIT_TIME]))
@@ -67,8 +67,4 @@ void GPIOSwitch::write_state(bool state) {
this->publish_state(state);
}
#ifdef USE_GPIO_SWITCH_INTERLOCK
void GPIOSwitch::set_interlock(const std::initializer_list<Switch *> &interlock) { this->interlock_ = interlock; }
#endif
} // namespace esphome::gpio
+5 -2
View File
@@ -21,7 +21,10 @@ class GPIOSwitch final : public switch_::Switch, public Component {
void setup() override;
void dump_config() override;
#ifdef USE_GPIO_SWITCH_INTERLOCK
void set_interlock(const std::initializer_list<Switch *> &interlock);
/// Codegen only: the table must outlive this switch; it may list this switch, which is skipped.
void set_interlock(Switch *const *interlock, size_t count) {
this->interlock_ = ConstVector<Switch *>(interlock, count);
}
void set_interlock_wait_time(uint32_t interlock_wait_time) { interlock_wait_time_ = interlock_wait_time; }
#endif
@@ -30,7 +33,7 @@ class GPIOSwitch final : public switch_::Switch, public Component {
GPIOPin *pin_{nullptr};
#ifdef USE_GPIO_SWITCH_INTERLOCK
FixedVector<Switch *> interlock_;
ConstVector<Switch *> interlock_;
uint32_t interlock_wait_time_{0};
#endif
};
@@ -0,0 +1,33 @@
"""Tests for the GPIO switch component."""
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
import re
def test_gpio_switch_interlock_group_shares_one_table(
generate_main: Callable[[str | Path], str],
) -> None:
"""Every member of an interlock group points at the same flash table."""
main_cpp = generate_main(
"tests/component_tests/gpio/test_gpio_switch_interlock.yaml"
)
tables = re.findall(r"(gpio_interlock\w*)\[\] PROGMEM = \{([^}]*)\};", main_cpp)
assert tables == [("gpio_interlock", "sw_a, sw_b, sw_c")]
for sw in ("sw_a", "sw_b", "sw_c"):
assert f"{sw}->set_interlock(gpio_interlock, 3);" in main_cpp
def test_gpio_switch_interlock_with_only_itself_has_no_table(
generate_main: Callable[[str | Path], str],
) -> None:
"""A switch interlocked only with itself gets no table and no set_interlock."""
main_cpp = generate_main(
"tests/component_tests/gpio/test_gpio_switch_interlock.yaml"
)
assert "sw_self->set_interlock(" not in main_cpp
assert "sw_self->set_interlock_wait_time(0);" in main_cpp
@@ -0,0 +1,23 @@
esphome:
name: test
esp32:
board: esp32dev
switch:
- platform: gpio
pin: 12
id: sw_a
interlock: [sw_b, sw_c]
- platform: gpio
pin: 13
id: sw_b
interlock: [sw_a, sw_c]
- platform: gpio
pin: 14
id: sw_c
interlock: [sw_a, sw_b]
- platform: gpio
pin: 15
id: sw_self
interlock: [sw_self]