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

Merge branch 'binary_sensor_gpio_polling' into integration

This commit is contained in:
J. Nick Koston
2025-06-17 13:31:44 +02:00
3 changed files with 119 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))
cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT]))
if config[CONF_USE_INTERRUPT]:
cg.add(var.set_interrupt_type(INTERRUPT_TYPES[config[CONF_INTERRUPT_TYPE]]))

View File

@@ -6,17 +6,78 @@ 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) {
pin->setup();
this->isr_pin_ = pin->to_isr();
// Read initial state
this->last_state_ = pin->digital_read();
this->state_ = this->last_state_;
// Attach interrupt - from this point on, any changes will be caught by the interrupt
pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, type);
}
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

@@ -2,14 +2,49 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace gpio {
// Store class for ISR data (no vtables, ISR-safe)
class GPIOBinarySensorStore {
public:
void setup(InternalGPIOPin *pin, gpio::InterruptType type);
static void gpio_intr(GPIOBinarySensorStore *arg);
bool get_state() const {
InterruptLock lock;
return this->state_;
}
bool has_changed() {
// No lock needed: single writer (ISR) / single reader (main loop) pattern
// Volatile bool operations are atomic on all ESPHome-supported platforms
if (!this->changed_) {
return false;
}
this->changed_ = false;
return true;
}
protected:
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:
// No destructor needed: ESPHome components are created at boot and live forever.
// Interrupts are only detached on reboot when memory is cleared anyway.
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 +57,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