mirror of
https://github.com/esphome/esphome.git
synced 2025-10-29 22:24:26 +00:00
Add enable_loop_soon_from_isr
This commit is contained in:
@@ -7,9 +7,13 @@ CODEOWNERS = ["@esphome/tests"]
|
||||
|
||||
loop_test_component_ns = cg.esphome_ns.namespace("loop_test_component")
|
||||
LoopTestComponent = loop_test_component_ns.class_("LoopTestComponent", cg.Component)
|
||||
LoopTestISRComponent = loop_test_component_ns.class_(
|
||||
"LoopTestISRComponent", cg.Component
|
||||
)
|
||||
|
||||
CONF_DISABLE_AFTER = "disable_after"
|
||||
CONF_TEST_REDUNDANT_OPERATIONS = "test_redundant_operations"
|
||||
CONF_ISR_COMPONENTS = "isr_components"
|
||||
|
||||
COMPONENT_CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
@@ -20,10 +24,18 @@ COMPONENT_CONFIG_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
ISR_COMPONENT_CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LoopTestISRComponent),
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LoopTestComponent),
|
||||
cv.Required(CONF_COMPONENTS): cv.ensure_list(COMPONENT_CONFIG_SCHEMA),
|
||||
cv.Optional(CONF_ISR_COMPONENTS): cv.ensure_list(ISR_COMPONENT_CONFIG_SCHEMA),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@@ -76,3 +88,9 @@ async def to_code(config):
|
||||
comp_config[CONF_TEST_REDUNDANT_OPERATIONS]
|
||||
)
|
||||
)
|
||||
|
||||
# Create ISR test components
|
||||
for isr_config in config.get(CONF_ISR_COMPONENTS, []):
|
||||
var = cg.new_Pvariable(isr_config[CONF_ID])
|
||||
await cg.register_component(var, isr_config)
|
||||
cg.add(var.set_name(isr_config[CONF_NAME]))
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "loop_test_isr_component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace loop_test_component {
|
||||
|
||||
static const char *const ISR_TAG = "loop_test_isr_component";
|
||||
|
||||
void LoopTestISRComponent::setup() {
|
||||
ESP_LOGI(ISR_TAG, "[%s] ISR component setup called", this->name_.c_str());
|
||||
this->last_check_time_ = millis();
|
||||
}
|
||||
|
||||
void LoopTestISRComponent::loop() {
|
||||
this->loop_count_++;
|
||||
ESP_LOGI(ISR_TAG, "[%s] ISR component loop count: %d", this->name_.c_str(), this->loop_count_);
|
||||
|
||||
// Disable after 5 loops
|
||||
if (this->loop_count_ == 5) {
|
||||
ESP_LOGI(ISR_TAG, "[%s] Disabling after 5 loops", this->name_.c_str());
|
||||
this->disable_loop();
|
||||
this->last_disable_time_ = millis();
|
||||
// Simulate ISR after disabling
|
||||
this->set_timeout("simulate_isr_1", 50, [this]() {
|
||||
ESP_LOGI(ISR_TAG, "[%s] Simulating ISR enable", this->name_.c_str());
|
||||
this->simulate_isr_enable();
|
||||
// Test reentrancy - call enable_loop() directly after ISR
|
||||
// This simulates another thread calling enable_loop while processing ISR enables
|
||||
this->set_timeout("test_reentrant", 10, [this]() {
|
||||
ESP_LOGI(ISR_TAG, "[%s] Testing reentrancy - calling enable_loop() directly", this->name_.c_str());
|
||||
this->enable_loop();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// If we get here after being disabled, it means ISR re-enabled us
|
||||
if (this->loop_count_ > 5 && this->loop_count_ < 10) {
|
||||
ESP_LOGI(ISR_TAG, "[%s] Running after ISR re-enable! ISR was called %d times", this->name_.c_str(),
|
||||
this->isr_call_count_);
|
||||
}
|
||||
|
||||
// Disable again after 10 loops to test multiple ISR enables
|
||||
if (this->loop_count_ == 10) {
|
||||
ESP_LOGI(ISR_TAG, "[%s] Disabling again after 10 loops", this->name_.c_str());
|
||||
this->disable_loop();
|
||||
this->last_disable_time_ = millis();
|
||||
|
||||
// Test pure ISR enable without any main loop enable
|
||||
this->set_timeout("simulate_isr_2", 50, [this]() {
|
||||
ESP_LOGI(ISR_TAG, "[%s] Testing pure ISR enable (no main loop enable)", this->name_.c_str());
|
||||
this->simulate_isr_enable();
|
||||
// DO NOT call enable_loop() - test that ISR alone works
|
||||
});
|
||||
}
|
||||
|
||||
// Log when we're running after second ISR enable
|
||||
if (this->loop_count_ > 10) {
|
||||
ESP_LOGI(ISR_TAG, "[%s] Running after pure ISR re-enable! ISR was called %d times total", this->name_.c_str(),
|
||||
this->isr_call_count_);
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR LoopTestISRComponent::simulate_isr_enable() {
|
||||
// This simulates what would happen in a real ISR
|
||||
// In a real scenario, this would be called from an actual interrupt handler
|
||||
|
||||
this->isr_call_count_++;
|
||||
|
||||
// Call enable_loop_soon_from_isr multiple times to test that it's safe
|
||||
this->enable_loop_soon_from_isr();
|
||||
this->enable_loop_soon_from_isr(); // Test multiple calls
|
||||
this->enable_loop_soon_from_isr(); // Should be idempotent
|
||||
|
||||
// Note: In a real ISR, we cannot use ESP_LOG* macros as they're not ISR-safe
|
||||
// For testing, we'll track the call count and log it from the main loop
|
||||
}
|
||||
|
||||
} // namespace loop_test_component
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace loop_test_component {
|
||||
|
||||
class LoopTestISRComponent : public Component {
|
||||
public:
|
||||
void set_name(const std::string &name) { this->name_ = name; }
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
// Simulates an ISR calling enable_loop_soon_from_isr
|
||||
void simulate_isr_enable();
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
std::string name_;
|
||||
int loop_count_{0};
|
||||
uint32_t last_disable_time_{0};
|
||||
uint32_t last_check_time_{0};
|
||||
bool isr_enable_pending_{false};
|
||||
int isr_call_count_{0};
|
||||
};
|
||||
|
||||
} // namespace loop_test_component
|
||||
} // namespace esphome
|
||||
Reference in New Issue
Block a user