mirror of
https://github.com/esphome/esphome.git
synced 2026-08-31 18:16:03 +00:00
Restructures Application::loop() into two independent phases to stop the scheduler from silently pulling the component loop cadence forward. Before: Application::loop() bounded its sleep by min(loop_interval_ - elapsed, next_schedule_in()) with a delay_time/2 floor. Any scheduler item due sooner than loop_interval_/2 dragged the whole component phase with it. On a typical ESP32 config with default loop_interval_=16ms, combined scheduler activity from api / esp32_ble / esp32_ble_tracker / debug was keeping every component's loop() running at ~128 Hz instead of the documented ~62 Hz. This has become more visible recently as more components convert to PollingComponent (which uses set_interval internally) and more in-tree code uses set_interval / set_timeout directly. Adding or removing any scheduled item silently changed every other component's loop cadence. App.set_loop_interval() for power savings was also silently defeated. After: - Phase A (every tick): drain wake notifications, run scheduler.call(), feed WDT - Phase B (gated by loop_interval_ or HighFrequencyLoopRequester): iterate registered components and update last_loop_ Sleep = min(time-until-next-component-phase, next_schedule_in()). When a scheduler event wakes us early, Phase A services it and the component phase stays gated independently. loop_interval_ is now a true minimum interval between component phases. The delay_time/2 floor is removed. Any legitimate need to wake faster than loop_interval_ has proper mechanisms: - HighFrequencyLoopRequester for sustained fast-loop needs - Application::wake_loop_threadsafe() from any context (new in 2026.4.0) for one-shot wake-on-event Also guards against set_interval(0) misuse — it asks the main loop to spin forever, which was never the intended API. Warns at creation time pointing authors at HighFrequencyLoopRequester. set_timeout(0)/defer() is unaffected; zero-delay one-shots remain legitimate. Runtime stats: process_pending_stats is now called on every tick (not just when the component phase runs) so log_interval_ isn't quantized to the component-phase cadence. Added an inline fast-path gate in runtime_stats.h that early-outs unless now >= next_log_time_, keeping Application::loop() slim; the log_stats_ work stays out-of-line. Ordering constraints preserved: - defer() callbacks still FIFO before components same-tick (Phase A runs before Phase B) - Scheduled items still execute before components when both due - Scheduled callbacks still run on main thread only - loop_component_start_time_ is still set fresh at each component's loop - WDT is still fed at least once per tick
Tests for ESPHome
This directory contains some tests for ESPHome.
At the moment, all the tests only work by simply executing
esphome over some YAML files that are made to test
whether the yaml gets converted to the proper C++ code.
Of course this is all just very high-level and things like unit tests would be much better. So if you have time and know how to set up a unit testing framework for python, please do give it a try.
When adding entries in test_.yaml files we usually need only
one file updated, unless conflicting code is generated for
different configurations, e.g. wifi and ethernet cannot
be tested on the same device.
Current test_.yaml file contents.
| Test name | Platform | Network | BLE |
|---|---|---|---|
| test1.yaml | ESP32 | wifi | None |
| test2.yaml | ESP32 | ethernet | esp32_ble_tracker |
| test3.yaml | ESP8266 | wifi | N/A |
| test4.yaml | ESP32 | ethernet | None |
| test5.yaml | ESP32 | wifi | ble_server |
| test6.yaml | RP2040 | wifi | N/A |
| test7.yaml | ESP32-C3 | wifi | N/A |
| test8.yaml | ESP32-S3 | wifi | None |
| test10.yaml | ESP32 | wifi | None |