esphome icon indicating copy to clipboard operation
esphome copied to clipboard

Add enable_loop_soon_any_context() for thread and ISR-safe loop enabling

Open bdraco opened this issue 6 months ago • 0 comments

What does this implement/fix?

This PR adds a new enable_loop_soon_any_context() method to the Component class, allowing components to safely request their loop() method to be re-enabled from interrupt service routines (ISRs), other threads, or any execution context.

Motivation

Currently, components can disable their loop() method to save CPU cycles when inactive using disable_loop(). However, there was no safe way to re-enable the loop from an ISR context (e.g., GPIO interrupts, timer callbacks). Calling enable_loop() directly from an ISR is unsafe because it:

  • Performs read-modify-write operations on component state
  • Calls into Application methods that manipulate data structures
  • Could cause race conditions or crashes

Implementation

The solution introduces:

  1. Component::enable_loop_soon_any_context() - A thread and ISR-safe method that defers the actual enable_loop() call to the main thread
  2. Volatile flags - Uses simple volatile variable assignments that are atomic on all platforms
  3. Efficient processing - A global flag (has_pending_enable_loop_requests_) avoids unnecessary iteration when no components have pending requests
  4. Race condition handling - The global flag is cleared before processing to ensure no requests are lost
  5. Memory optimization - Component member variables reordered to eliminate padding on 32-bit systems (saves 8 bytes per component)

ISR Safety

The method is ISR-safe because it:

  • Only performs simple assignments to volatile variables (atomic on all platforms)
  • Contains no read-modify-write operations that could be interrupted
  • Does no memory allocation, object construction, or function calls
  • Is marked with IRAM_ATTR to ensure code is in IRAM for ISR execution
  • Takes advantage of components never being destroyed in ESPHome

Design Decisions

  • No disable_loop_soon_any_context() - This is intentional. Disable operations would race against enable calls and synchronization would become too complex. Disabling should only be done from the main thread.
  • "soon" in the name - Clearly indicates the operation is deferred, not immediate
  • Volatile over std::atomic - Ensures compatibility across all platforms (ESP8266, ESP32, LibreTiny, RP2040)

Example Usage

class ExampleISRComponent : public Component {
 protected:
  void IRAM_ATTR HOT gpio_interrupt_handler() {
    // Request loop to be enabled on next main loop iteration
    this->enable_loop_soon_any_context();
    // The actual enable_loop() will happen safely in the main thread
  }
};

Use Cases

This addresses the need for components that:

  • Monitor hardware interrupts but don't need continuous loop() execution
  • Want to save power by disabling loop() when idle
  • Need to respond quickly to external events

Examples include interrupt-driven sensors, event-based communication protocols, and power-sensitive battery-operated devices.

Types of changes

  • [ ] Bugfix (non-breaking change which fixes an issue)
  • [x] New feature (non-breaking change which adds functionality)
  • [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • [ ] Other

Related issue or feature (if applicable): fixes

N/A

Pull request in esphome-docs with documentation (if applicable): esphome/esphome-docs#

N/A - This is primarily an internal API for component developers. The method is documented inline with comprehensive docstrings.

Test Environment

  • [ ] ESP32
  • [x] ESP32 IDF
  • [x] ESP8266
  • [ ] RP2040
  • [ ] BK72xx
  • [ ] RTL87xx

Example entry for config.yaml:

# Not applicable - this is an internal API change

Checklist:

  • [x] The code change is tested and works locally.
  • [x] Tests have been added to verify that the new code works (under tests/ folder).
  • [ ] Documentation added/updated as needed.
  • [x] I have read the contributing guide.

bdraco avatar Jun 18 '25 06:06 bdraco