Reduce number of calls to fetch time in the main loop (#8804)

This commit is contained in:
J. Nick Koston 2025-05-18 15:48:57 -04:00 committed by GitHub
parent e47741d471
commit 574aabdede
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 107 additions and 53 deletions

View File

@ -8,6 +8,7 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
#include "esphome/core/application.h"
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
@ -146,7 +147,7 @@ void APIConnection::loop() {
}
return;
} else {
this->last_traffic_ = millis();
this->last_traffic_ = App.get_loop_component_start_time();
// read a packet
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
if (this->remove_)
@ -165,7 +166,7 @@ void APIConnection::loop() {
static uint32_t keepalive = 60000;
static uint8_t max_ping_retries = 60;
static uint16_t ping_retry_interval = 1000;
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (this->sent_ping_) {
// Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > (keepalive * 5) / 2) {

View File

@ -3,6 +3,7 @@
#include "bedjet_hub.h"
#include "bedjet_child.h"
#include "bedjet_const.h"
#include "esphome/core/application.h"
#include <cinttypes>
namespace esphome {

View File

@ -2,6 +2,7 @@
#include "esphome/core/log.h"
#include "esphome/core/macros.h"
#include "esphome/core/application.h"
#ifdef USE_ESP32
@ -177,7 +178,7 @@ void BluetoothProxy::loop() {
// Flush any pending BLE advertisements that have been accumulated but not yet sent
if (this->raw_advertisements_) {
static uint32_t last_flush_time = 0;
uint32_t now = millis();
uint32_t now = App.get_loop_component_start_time();
// Flush accumulated advertisements every 100ms
if (now - last_flush_time >= 100) {

View File

@ -1,5 +1,6 @@
#include "cse7766.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace cse7766 {
@ -7,7 +8,7 @@ namespace cse7766 {
static const char *const TAG = "cse7766";
void CSE7766Component::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_transmission_ >= 500) {
// last transmission too long ago. Reset RX index.
this->raw_data_index_ = 0;

View File

@ -1,6 +1,7 @@
#include "current_based_cover.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <cfloat>
namespace esphome {
@ -60,7 +61,7 @@ void CurrentBasedCover::loop() {
if (this->current_operation == COVER_OPERATION_IDLE)
return;
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (this->current_operation == COVER_OPERATION_OPENING) {
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction

View File

@ -1,6 +1,7 @@
#include "daly_bms.h"
#include <vector>
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace daly_bms {
@ -32,7 +33,7 @@ void DalyBmsComponent::update() {
}
void DalyBmsComponent::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (this->receiving_ && (now - this->last_transmission_ >= 200)) {
// last transmission too long ago. Reset RX index.
ESP_LOGW(TAG, "Last transmission too long ago. Reset RX index.");

View File

@ -70,7 +70,7 @@ void DebugComponent::loop() {
#ifdef USE_SENSOR
// calculate loop time - from last call to this one
if (this->loop_time_sensor_ != nullptr) {
uint32_t now = millis();
uint32_t now = App.get_loop_component_start_time();
uint32_t loop_time = now - this->last_loop_timetag_;
this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
this->last_loop_timetag_ = now;

View File

@ -1,6 +1,7 @@
#include "endstop_cover.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/application.h"
namespace esphome {
namespace endstop {
@ -65,7 +66,7 @@ void EndstopCover::loop() {
if (this->current_operation == COVER_OPERATION_IDLE)
return;
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (this->current_operation == COVER_OPERATION_OPENING && this->is_open_()) {
float dur = (now - this->start_dir_time_) / 1e3f;

View File

@ -6,6 +6,7 @@
#include <cstring>
#include "ble_uuid.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace esp32_ble {
@ -143,7 +144,7 @@ void BLEAdvertising::loop() {
if (this->raw_advertisements_callbacks_.empty()) {
return;
}
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) {
this->stop();
this->current_adv_index_ += 1;

View File

@ -3,6 +3,7 @@
#include "esp32_camera.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/application.h"
#include <freertos/task.h>
@ -162,7 +163,7 @@ void ESP32Camera::loop() {
}
// request idle image every idle_update_interval
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
this->last_idle_request_ = now;
this->request_image(IDLE);

View File

@ -92,7 +92,7 @@ void ESP32ImprovComponent::loop() {
if (!this->incoming_data_.empty())
this->process_incoming_data_();
uint32_t now = millis();
uint32_t now = App.get_loop_component_start_time();
switch (this->state_) {
case improv::STATE_STOPPED:

View File

@ -288,7 +288,7 @@ uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) {
}
void ESP32TouchComponent::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250;
for (auto *child : this->children_) {
child->value_ = this->component_touch_pad_read(child->get_touch_pad());

View File

@ -240,7 +240,7 @@ void EthernetComponent::setup() {
}
void EthernetComponent::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
switch (this->state_) {
case EthernetComponentState::STOPPED:

View File

@ -1,6 +1,7 @@
#include "feedback_cover.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace feedback {
@ -220,7 +221,7 @@ void FeedbackCover::set_open_obstacle_sensor(binary_sensor::BinarySensor *open_o
void FeedbackCover::loop() {
if (this->current_operation == COVER_OPERATION_IDLE)
return;
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
// Recompute position every loop cycle
this->recompute_position_();

View File

@ -6,6 +6,7 @@
*/
#include "gcja5.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <cstring>
namespace esphome {
@ -16,7 +17,7 @@ static const char *const TAG = "gcja5";
void GCJA5Component::setup() { ESP_LOGCONFIG(TAG, "Setting up gcja5..."); }
void GCJA5Component::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_transmission_ >= 500) {
// last transmission too long ago. Reset RX index.
this->rx_message_.clear();

View File

@ -1,5 +1,6 @@
#include "growatt_solar.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace growatt_solar {
@ -18,7 +19,7 @@ void GrowattSolar::loop() {
void GrowattSolar::update() {
// If our last send has had no reply yet, and it wasn't that long ago, do nothing.
uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_send_ < this->get_update_interval() / 2) {
return;
}

View File

@ -1,5 +1,6 @@
#include "kuntze.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace kuntze {
@ -60,7 +61,7 @@ void Kuntze::on_modbus_data(const std::vector<uint8_t> &data) {
}
void Kuntze::loop() {
uint32_t now = millis();
uint32_t now = App.get_loop_component_start_time();
// timeout after 15 seconds
if (this->waiting_ && (now - this->last_send_ > 15000)) {
ESP_LOGW(TAG, "timed out waiting for response");

View File

@ -1,5 +1,6 @@
#include "matrix_keypad.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace matrix_keypad {
@ -28,7 +29,7 @@ void MatrixKeypad::setup() {
void MatrixKeypad::loop() {
static uint32_t active_start = 0;
static int active_key = -1;
uint32_t now = millis();
uint32_t now = App.get_loop_component_start_time();
int key = -1;
bool error = false;
int pos = 0, row, col;

View File

@ -2,6 +2,7 @@
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/hal.h"
#include "esphome/core/application.h"
#include "max7219font.h"
#include <algorithm>
@ -63,7 +64,7 @@ void MAX7219Component::dump_config() {
}
void MAX7219Component::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
const uint32_t millis_since_last_scroll = now - this->last_scroll_;
const size_t first_line_size = this->max_displaybuffer_[0].size();
// check if the buffer has shrunk past the current position since last update

View File

@ -1,6 +1,7 @@
#include "modbus.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/application.h"
namespace esphome {
namespace modbus {
@ -13,7 +14,7 @@ void Modbus::setup() {
}
}
void Modbus::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
while (this->available()) {
uint8_t byte;

View File

@ -345,7 +345,7 @@ void MQTTClientComponent::loop() {
this->disconnect_reason_.reset();
}
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
switch (this->state_) {
case MQTT_CLIENT_DISABLED:

View File

@ -1,5 +1,6 @@
#include "pmsx003.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace pmsx003 {
@ -42,7 +43,7 @@ void PMSX003Component::dump_config() {
}
void PMSX003Component::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
// If we update less often than it takes the device to stabilise, spin the fan down
// rather than running it constantly. It does take some time to stabilise, so we

View File

@ -1,5 +1,6 @@
#include "pzem004t.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <cinttypes>
namespace esphome {
@ -16,7 +17,7 @@ void PZEM004T::setup() {
}
void PZEM004T::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_read_ > 500 && this->available() < 7) {
while (this->available())
this->read();

View File

@ -1,5 +1,6 @@
#include "rf_bridge.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <cinttypes>
#include <cstring>
@ -128,7 +129,7 @@ void RFBridgeComponent::write_byte_str_(const std::string &codes) {
}
void RFBridgeComponent::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_bridge_byte_ > 50) {
this->rx_buffer_.clear();
this->last_bridge_byte_ = now;

View File

@ -1,5 +1,6 @@
#include "sds011.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace sds011 {
@ -75,7 +76,7 @@ void SDS011Component::dump_config() {
}
void SDS011Component::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if ((now - this->last_transmission_ >= 500) && this->data_index_) {
// last transmission too long ago. Reset RX index.
ESP_LOGV(TAG, "Last transmission too long ago. Reset RX index.");

View File

@ -1,5 +1,6 @@
#include "slow_pwm_output.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace slow_pwm {
@ -39,7 +40,7 @@ void SlowPWMOutput::set_output_state_(bool new_state) {
}
void SlowPWMOutput::loop() {
uint32_t now = millis();
uint32_t now = App.get_loop_component_start_time();
float scaled_state = this->state_ * this->period_;
if (now - this->period_start_time_ >= this->period_) {

View File

@ -20,7 +20,7 @@ SprinklerSwitch::SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *o
bool SprinklerSwitch::is_latching_valve() { return (this->off_switch_ != nullptr) && (this->on_switch_ != nullptr); }
void SprinklerSwitch::loop() {
if ((this->pinned_millis_) && (millis() > this->pinned_millis_ + this->pulse_duration_)) {
if ((this->pinned_millis_) && (App.get_loop_component_start_time() > this->pinned_millis_ + this->pulse_duration_)) {
this->pinned_millis_ = 0; // reset tracker
if (this->off_switch_->state) {
this->off_switch_->turn_off();
@ -148,22 +148,23 @@ SprinklerValveOperator::SprinklerValveOperator(SprinklerValve *valve, Sprinkler
: controller_(controller), valve_(valve) {}
void SprinklerValveOperator::loop() {
if (millis() >= this->start_millis_) { // dummy check
uint32_t now = App.get_loop_component_start_time();
if (now >= this->start_millis_) { // dummy check
switch (this->state_) {
case STARTING:
if (millis() > (this->start_millis_ + this->start_delay_)) {
if (now > (this->start_millis_ + this->start_delay_)) {
this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
}
break;
case ACTIVE:
if (millis() > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
if (now > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
}
break;
case STOPPING:
if (millis() > (this->stop_millis_ + this->stop_delay_)) {
if (now > (this->stop_millis_ + this->stop_delay_)) {
this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
}
break;

View File

@ -1,6 +1,7 @@
#include "time_based_cover.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/application.h"
namespace esphome {
namespace time_based {
@ -26,7 +27,7 @@ void TimeBasedCover::loop() {
if (this->current_operation == COVER_OPERATION_IDLE)
return;
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
// Recompute position every loop cycle
this->recompute_position_();

View File

@ -1,5 +1,6 @@
#include "uart_switch.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace uart {
@ -8,7 +9,7 @@ static const char *const TAG = "uart.switch";
void UARTSwitch::loop() {
if (this->send_every_) {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_transmission_ > this->send_every_) {
this->write_command_(this->state);
this->last_transmission_ = now;

View File

@ -1,6 +1,7 @@
#include "uponor_smatrix_climate.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace uponor_smatrix {
@ -13,7 +14,7 @@ void UponorSmatrixClimate::dump_config() {
}
void UponorSmatrixClimate::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
// Publish state after all update packets are processed
if (this->last_data_ != 0 && (now - this->last_data_ > 100) && this->target_temperature_raw_ != 0) {

View File

@ -1,5 +1,6 @@
#include "uponor_smatrix.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace uponor_smatrix {
@ -35,7 +36,7 @@ void UponorSmatrixComponent::dump_config() {
}
void UponorSmatrixComponent::loop() {
const uint32_t now = millis();
const uint32_t now = App.get_loop_component_start_time();
// Discard stale data
if (!this->rx_buffer_.empty() && (now - this->last_rx_ > 50)) {

View File

@ -67,22 +67,32 @@ void Application::loop() {
uint32_t new_app_state = 0;
this->scheduler.call();
this->feed_wdt();
// Get the initial loop time at the start
uint32_t last_op_end_time = millis();
// Feed WDT with time
this->feed_wdt(last_op_end_time);
for (Component *component : this->looping_components_) {
// Update the cached time before each component runs
this->loop_component_start_time_ = last_op_end_time;
{
this->set_current_component(component);
WarnIfComponentBlockingGuard guard{component};
WarnIfComponentBlockingGuard guard{component, last_op_end_time};
component->call();
// Use the finish method to get the current time as the end time
last_op_end_time = guard.finish();
}
new_app_state |= component->get_component_state();
this->app_state_ |= new_app_state;
this->feed_wdt();
this->feed_wdt(last_op_end_time);
}
this->app_state_ = new_app_state;
const uint32_t now = millis();
auto elapsed = now - this->last_loop_;
// Use the last component's end time instead of calling millis() again
auto elapsed = last_op_end_time - this->last_loop_;
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
yield();
} else {
@ -94,7 +104,7 @@ void Application::loop() {
delay_time = std::min(next_schedule, delay_time);
delay(delay_time);
}
this->last_loop_ = now;
this->last_loop_ = last_op_end_time;
if (this->dump_config_at_ < this->components_.size()) {
if (this->dump_config_at_ == 0) {
@ -109,10 +119,12 @@ void Application::loop() {
}
}
void IRAM_ATTR HOT Application::feed_wdt() {
void IRAM_ATTR HOT Application::feed_wdt(uint32_t time) {
static uint32_t last_feed = 0;
uint32_t now = micros();
if (now - last_feed > 3000) {
// Use provided time if available, otherwise get current time
uint32_t now = time ? time : millis();
// Compare in milliseconds (3ms threshold)
if (now - last_feed > 3) {
arch_feed_wdt();
last_feed = now;
#ifdef USE_STATUS_LED

View File

@ -217,6 +217,9 @@ class Application {
std::string get_compilation_time() const { return this->compilation_time_; }
/// Get the cached time in milliseconds from when the current component started its loop execution
inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; }
/** Set the target interval with which to run the loop() calls.
* If the loop() method takes longer than the target interval, ESPHome won't
* sleep in loop(), but if the time spent in loop() is small than the target, ESPHome
@ -236,7 +239,7 @@ class Application {
void schedule_dump_config() { this->dump_config_at_ = 0; }
void feed_wdt();
void feed_wdt(uint32_t time = 0);
void reboot();
@ -551,6 +554,7 @@ class Application {
size_t dump_config_at_{SIZE_MAX};
uint32_t app_state_{0};
Component *current_component_{nullptr};
uint32_t loop_component_start_time_{0};
};
/// Global storage of Application pointer - only one Application can exist.

View File

@ -240,10 +240,12 @@ void PollingComponent::stop_poller() {
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component)
: started_(millis()), component_(component) {}
WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
uint32_t blocking_time = millis() - this->started_;
WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component, uint32_t start_time)
: started_(start_time), component_(component) {}
uint32_t WarnIfComponentBlockingGuard::finish() {
uint32_t curr_time = millis();
uint32_t blocking_time = curr_time - this->started_;
bool should_warn;
if (this->component_ != nullptr) {
should_warn = this->component_->should_warn_of_blocking(blocking_time);
@ -254,8 +256,11 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, blocking_time);
ESP_LOGW(TAG, "Components should block for at most 30 ms.");
;
}
return curr_time;
}
WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {}
} // namespace esphome

View File

@ -339,7 +339,11 @@ class PollingComponent : public Component {
class WarnIfComponentBlockingGuard {
public:
WarnIfComponentBlockingGuard(Component *component);
WarnIfComponentBlockingGuard(Component *component, uint32_t start_time);
// Finish the timing operation and return the current time
uint32_t finish();
~WarnIfComponentBlockingGuard();
protected:

View File

@ -229,8 +229,11 @@ void HOT Scheduler::call() {
// - timeouts/intervals get added, potentially invalidating vector pointers
// - timeouts/intervals get cancelled
{
WarnIfComponentBlockingGuard guard{item->component};
uint32_t now_ms = millis();
WarnIfComponentBlockingGuard guard{item->component, now_ms};
item->callback();
// Call finish to ensure blocking time is properly calculated and reported
guard.finish();
}
}