1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-14 17:22:20 +01:00

Avoid polling for GPIO binary sensors when possible

This commit is contained in:
J. Nick Koston
2025-06-17 12:41:17 +02:00
parent 5ffe50381a
commit d4db16665f
3 changed files with 129 additions and 3 deletions

View File

@@ -10,11 +10,24 @@ GPIOBinarySensor = gpio_ns.class_(
"GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component
)
CONF_USE_INTERRUPT = "use_interrupt"
CONF_INTERRUPT_TYPE = "interrupt_type"
INTERRUPT_TYPES = {
"RISING": gpio_ns.INTERRUPT_RISING_EDGE,
"FALLING": gpio_ns.INTERRUPT_FALLING_EDGE,
"ANY": gpio_ns.INTERRUPT_ANY_EDGE,
}
CONFIG_SCHEMA = (
binary_sensor.binary_sensor_schema(GPIOBinarySensor)
.extend(
{
cv.Required(CONF_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_USE_INTERRUPT, default=True): cv.boolean,
cv.Optional(CONF_INTERRUPT_TYPE, default="ANY"): cv.enum(
INTERRUPT_TYPES, upper=True
),
}
)
.extend(cv.COMPONENT_SCHEMA)
@@ -27,3 +40,7 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
if config[CONF_USE_INTERRUPT]:
cg.add(var.set_use_interrupt(True))
cg.add(var.set_interrupt_type(INTERRUPT_TYPES[config[CONF_INTERRUPT_TYPE]]))

View File

@@ -6,17 +6,91 @@ namespace gpio {
static const char *const TAG = "gpio.binary_sensor";
void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) {
bool new_state = arg->isr_pin_.digital_read();
if (new_state != arg->last_state_) {
arg->state_ = new_state;
arg->last_state_ = new_state;
arg->changed_ = true;
}
}
void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, gpio::InterruptType type) {
this->pin_ = pin;
pin->setup();
this->isr_pin_ = pin->to_isr();
{
InterruptLock lock;
this->last_state_ = pin->digital_read();
this->state_ = this->last_state_;
}
pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, type);
}
void GPIOBinarySensorStore::detach() {
if (this->pin_ != nullptr) {
this->pin_->detach_interrupt();
this->pin_ = nullptr;
}
}
GPIOBinarySensor::~GPIOBinarySensor() {
if (this->use_interrupt_) {
this->store_.detach();
}
}
void GPIOBinarySensor::setup() {
this->pin_->setup();
this->publish_initial_state(this->pin_->digital_read());
if (this->use_interrupt_ && !this->pin_->is_internal()) {
ESP_LOGW(TAG, "Interrupts not supported for this pin type, falling back to polling");
this->use_interrupt_ = false;
}
if (this->use_interrupt_) {
auto *internal_pin = static_cast<InternalGPIOPin *>(this->pin_);
this->store_.setup(internal_pin, this->interrupt_type_);
this->publish_initial_state(this->store_.get_state());
} else {
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_);
const char *mode = this->use_interrupt_ ? "interrupt" : "polling";
ESP_LOGCONFIG(TAG, " Mode: %s", mode);
if (this->use_interrupt_) {
const char *interrupt_type;
switch (this->interrupt_type_) {
case gpio::INTERRUPT_RISING_EDGE:
interrupt_type = "RISING_EDGE";
break;
case gpio::INTERRUPT_FALLING_EDGE:
interrupt_type = "FALLING_EDGE";
break;
case gpio::INTERRUPT_ANY_EDGE:
interrupt_type = "ANY_EDGE";
break;
default:
interrupt_type = "UNKNOWN";
break;
}
ESP_LOGCONFIG(TAG, " Interrupt Type: %s", interrupt_type);
}
}
void GPIOBinarySensor::loop() { this->publish_state(this->pin_->digital_read()); }
void GPIOBinarySensor::loop() {
if (this->use_interrupt_) {
if (this->store_.has_changed()) {
bool state = this->store_.get_state();
this->publish_state(state);
}
} else {
this->publish_state(this->pin_->digital_read());
}
}
float GPIOBinarySensor::get_setup_priority() const { return setup_priority::HARDWARE; }

View File

@@ -7,9 +7,41 @@
namespace esphome {
namespace gpio {
// Store class for ISR data (no vtables, ISR-safe)
class GPIOBinarySensorStore {
public:
void setup(InternalGPIOPin *pin, gpio::InterruptType type);
void detach();
static void gpio_intr(GPIOBinarySensorStore *arg);
bool get_state() const {
InterruptLock lock;
return this->state_;
}
bool has_changed() {
InterruptLock lock;
bool changed = this->changed_;
this->changed_ = false;
return changed;
}
protected:
InternalGPIOPin *pin_{nullptr};
ISRInternalGPIOPin isr_pin_;
volatile bool state_{false};
volatile bool last_state_{false};
volatile bool changed_{false};
};
class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component {
public:
~GPIOBinarySensor() override;
void set_pin(GPIOPin *pin) { pin_ = pin; }
void set_use_interrupt(bool use_interrupt) { use_interrupt_ = use_interrupt; }
void set_interrupt_type(gpio::InterruptType type) { interrupt_type_ = type; }
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/// Setup pin
@@ -22,6 +54,9 @@ class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component {
protected:
GPIOPin *pin_;
bool use_interrupt_{true};
gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE};
GPIOBinarySensorStore store_;
};
} // namespace gpio