mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Avoid polling for GPIO binary sensors when possible
This commit is contained in:
		| @@ -10,11 +10,24 @@ GPIOBinarySensor = gpio_ns.class_( | |||||||
|     "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component |     "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 = ( | CONFIG_SCHEMA = ( | ||||||
|     binary_sensor.binary_sensor_schema(GPIOBinarySensor) |     binary_sensor.binary_sensor_schema(GPIOBinarySensor) | ||||||
|     .extend( |     .extend( | ||||||
|         { |         { | ||||||
|             cv.Required(CONF_PIN): pins.gpio_input_pin_schema, |             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) |     .extend(cv.COMPONENT_SCHEMA) | ||||||
| @@ -27,3 +40,7 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     pin = await cg.gpio_pin_expression(config[CONF_PIN]) |     pin = await cg.gpio_pin_expression(config[CONF_PIN]) | ||||||
|     cg.add(var.set_pin(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]])) | ||||||
|   | |||||||
| @@ -6,17 +6,91 @@ namespace gpio { | |||||||
|  |  | ||||||
| static const char *const TAG = "gpio.binary_sensor"; | 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() { | void GPIOBinarySensor::setup() { | ||||||
|  |   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->pin_->setup(); | ||||||
|     this->publish_initial_state(this->pin_->digital_read()); |     this->publish_initial_state(this->pin_->digital_read()); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void GPIOBinarySensor::dump_config() { | void GPIOBinarySensor::dump_config() { | ||||||
|   LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this); |   LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this); | ||||||
|   LOG_PIN("  Pin: ", this->pin_); |   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; } | float GPIOBinarySensor::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,9 +7,41 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace gpio { | 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 { | class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component { | ||||||
|  public: |  public: | ||||||
|  |   ~GPIOBinarySensor() override; | ||||||
|  |  | ||||||
|   void set_pin(GPIOPin *pin) { pin_ = pin; } |   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 ========== |   // ========== INTERNAL METHODS ========== | ||||||
|   // (In most use cases you won't need these) |   // (In most use cases you won't need these) | ||||||
|   /// Setup pin |   /// Setup pin | ||||||
| @@ -22,6 +54,9 @@ class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   GPIOPin *pin_; |   GPIOPin *pin_; | ||||||
|  |   bool use_interrupt_{true}; | ||||||
|  |   gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE}; | ||||||
|  |   GPIOBinarySensorStore store_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace gpio | }  // namespace gpio | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user