esphome 2025.5.0b2__py3-none-any.whl → 2025.5.0b4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. esphome/__main__.py +16 -14
  2. esphome/components/api/api_connection.cpp +339 -652
  3. esphome/components/api/api_connection.h +251 -57
  4. esphome/components/api/api_frame_helper.cpp +136 -49
  5. esphome/components/api/api_frame_helper.h +49 -6
  6. esphome/components/api/proto.h +48 -8
  7. esphome/components/ballu/climate.py +1 -1
  8. esphome/components/bedjet/bedjet_hub.cpp +1 -0
  9. esphome/components/binary_sensor/binary_sensor.cpp +10 -6
  10. esphome/components/binary_sensor/binary_sensor.h +1 -1
  11. esphome/components/binary_sensor/filter.cpp +21 -21
  12. esphome/components/binary_sensor/filter.h +10 -10
  13. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +2 -1
  14. esphome/components/ccs811/sensor.py +9 -6
  15. esphome/components/climate_ir/__init__.py +3 -3
  16. esphome/components/climate_ir_lg/climate.py +1 -1
  17. esphome/components/coolix/climate.py +1 -1
  18. esphome/components/cse7766/cse7766.cpp +2 -1
  19. esphome/components/current_based/current_based_cover.cpp +2 -1
  20. esphome/components/daikin/climate.py +1 -1
  21. esphome/components/daikin_arc/climate.py +1 -1
  22. esphome/components/daikin_brc/climate.py +1 -1
  23. esphome/components/daly_bms/daly_bms.cpp +2 -1
  24. esphome/components/debug/debug_component.cpp +1 -1
  25. esphome/components/delonghi/climate.py +1 -1
  26. esphome/components/dps310/sensor.py +6 -6
  27. esphome/components/ee895/sensor.py +9 -9
  28. esphome/components/emmeti/climate.py +1 -1
  29. esphome/components/endstop/endstop_cover.cpp +2 -1
  30. esphome/components/ens160_base/__init__.py +12 -9
  31. esphome/components/esp32_ble/ble_advertising.cpp +2 -1
  32. esphome/components/esp32_camera/__init__.py +1 -1
  33. esphome/components/esp32_camera/esp32_camera.cpp +2 -10
  34. esphome/components/esp32_camera/esp32_camera.h +1 -1
  35. esphome/components/esp32_improv/esp32_improv_component.cpp +1 -1
  36. esphome/components/esp32_touch/esp32_touch.cpp +1 -1
  37. esphome/components/ethernet/ethernet_component.cpp +1 -1
  38. esphome/components/feedback/feedback_cover.cpp +2 -1
  39. esphome/components/fujitsu_general/climate.py +1 -1
  40. esphome/components/gcja5/gcja5.cpp +2 -1
  41. esphome/components/gps/__init__.py +37 -16
  42. esphome/components/gps/gps.cpp +33 -17
  43. esphome/components/gps/gps.h +16 -15
  44. esphome/components/gree/climate.py +1 -1
  45. esphome/components/growatt_solar/growatt_solar.cpp +2 -1
  46. esphome/components/heatpumpir/climate.py +1 -1
  47. esphome/components/hitachi_ac344/climate.py +1 -1
  48. esphome/components/hitachi_ac424/climate.py +1 -1
  49. esphome/components/hte501/sensor.py +6 -6
  50. esphome/components/hyt271/sensor.py +6 -6
  51. esphome/components/kuntze/kuntze.cpp +2 -1
  52. esphome/components/logger/__init__.py +1 -0
  53. esphome/components/logger/logger.cpp +53 -32
  54. esphome/components/logger/logger.h +55 -5
  55. esphome/components/matrix_keypad/matrix_keypad.cpp +2 -1
  56. esphome/components/max7219digit/max7219digit.cpp +2 -1
  57. esphome/components/mhz19/sensor.py +11 -7
  58. esphome/components/midea_ir/climate.py +1 -1
  59. esphome/components/mitsubishi/climate.py +1 -1
  60. esphome/components/modbus/modbus.cpp +2 -1
  61. esphome/components/mqtt/mqtt_client.cpp +1 -1
  62. esphome/components/ms5611/sensor.py +6 -6
  63. esphome/components/ms8607/sensor.py +3 -3
  64. esphome/components/noblex/climate.py +1 -1
  65. esphome/components/pmsx003/pmsx003.cpp +2 -1
  66. esphome/components/pzem004t/pzem004t.cpp +2 -1
  67. esphome/components/rf_bridge/rf_bridge.cpp +2 -1
  68. esphome/components/sds011/sds011.cpp +2 -1
  69. esphome/components/sen5x/sen5x.cpp +55 -36
  70. esphome/components/senseair/sensor.py +3 -3
  71. esphome/components/sgp30/sensor.py +14 -16
  72. esphome/components/shtcx/sensor.py +6 -6
  73. esphome/components/slow_pwm/slow_pwm_output.cpp +2 -1
  74. esphome/components/sprinkler/sprinkler.cpp +6 -5
  75. esphome/components/t6615/sensor.py +3 -3
  76. esphome/components/t6615/t6615.cpp +2 -1
  77. esphome/components/tcl112/climate.py +1 -1
  78. esphome/components/time_based/time_based_cover.cpp +2 -1
  79. esphome/components/toshiba/climate.py +1 -1
  80. esphome/components/uart/switch/uart_switch.cpp +2 -1
  81. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +2 -1
  82. esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
  83. esphome/components/weikai/weikai.cpp +0 -52
  84. esphome/components/whirlpool/climate.py +1 -1
  85. esphome/components/whynter/climate.py +1 -1
  86. esphome/components/zhlt01/climate.py +1 -1
  87. esphome/config.py +13 -13
  88. esphome/const.py +1 -1
  89. esphome/core/application.cpp +26 -10
  90. esphome/core/application.h +5 -1
  91. esphome/core/component.cpp +10 -5
  92. esphome/core/component.h +5 -1
  93. esphome/core/doxygen.h +13 -0
  94. esphome/core/scheduler.cpp +4 -1
  95. esphome/log.py +15 -19
  96. esphome/mqtt.py +2 -2
  97. esphome/voluptuous_schema.py +3 -1
  98. esphome/wizard.py +45 -35
  99. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b4.dist-info}/METADATA +1 -1
  100. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b4.dist-info}/RECORD +104 -103
  101. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b4.dist-info}/WHEEL +0 -0
  102. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b4.dist-info}/entry_points.txt +0 -0
  103. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b4.dist-info}/licenses/LICENSE +0 -0
  104. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b4.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  #include "uponor_smatrix.h"
2
2
  #include "esphome/core/log.h"
3
+ #include "esphome/core/application.h"
3
4
 
4
5
  namespace esphome {
5
6
  namespace uponor_smatrix {
@@ -35,7 +36,7 @@ void UponorSmatrixComponent::dump_config() {
35
36
  }
36
37
 
37
38
  void UponorSmatrixComponent::loop() {
38
- const uint32_t now = millis();
39
+ const uint32_t now = App.get_loop_component_start_time();
39
40
 
40
41
  // Discard stale data
41
42
  if (!this->rx_buffer_.empty() && (now - this->last_rx_ > 50)) {
@@ -8,58 +8,6 @@
8
8
  namespace esphome {
9
9
  namespace weikai {
10
10
 
11
- /*! @mainpage Weikai source code documentation
12
- This documentation provides information about the implementation of the family of WeiKai Components in ESPHome.
13
- Here is the class diagram related to Weikai family of components:
14
- @image html weikai_class.png
15
-
16
- @section WKRingBuffer_ The WKRingBuffer template class
17
- The WKRingBuffer template class has it names implies implement a simple ring buffer helper class. This straightforward
18
- container implements FIFO functionality, enabling bytes to be pushed into one side and popped from the other in the
19
- order of entry. Implementation is classic and therefore not described in any details.
20
-
21
- @section WeikaiRegister_ The WeikaiRegister class
22
- The WeikaiRegister helper class creates objects that act as proxies to the device registers.
23
- @details This is an abstract virtual class (interface) that provides all the necessary access to registers while hiding
24
- the actual implementation. The access to the registers can be made through an I²C bus in for example for wk2168_i2c
25
- component or through a SPI bus for example in the case of the wk2168_spi component. Derived classes will actually
26
- performs the specific bus operations.
27
-
28
- @section WeikaiRegisterI2C_ WeikaiRegisterI2C
29
- The weikai_i2c::WeikaiRegisterI2C class implements the virtual methods of the WeikaiRegister class for an I2C bus.
30
-
31
- @section WeikaiRegisterSPI_ WeikaiRegisterSPI
32
- The weikai_spi::WeikaiRegisterSPI class implements the virtual methods of the WeikaiRegister class for an SPI bus.
33
-
34
- @section WeikaiComponent_ The WeikaiComponent class
35
- The WeikaiComponent class stores the information global to a WeiKai family component and provides methods to set/access
36
- this information. It also serves as a container for WeikaiChannel instances. This is done by maintaining an array of
37
- references these WeikaiChannel instances. This class derives from the esphome::Component classes. This class override
38
- esphome::Component::loop() method to facilitate the seamless transfer of accumulated bytes from the receive
39
- FIFO into the ring buffer. This process ensures quick access to the stored bytes, enhancing the overall efficiency of
40
- the component.
41
-
42
- @section WeikaiComponentI2C_ WeikaiComponentI2C
43
- The weikai_i2c::WeikaiComponentI2C class implements the virtual methods of the WeikaiComponent class for an I2C bus.
44
-
45
- @section WeikaiComponentSPI_ WeikaiComponentSPI
46
- The weikai_spi::WeikaiComponentSPI class implements the virtual methods of the WeikaiComponent class for an SPI bus.
47
-
48
- @section WeikaiGPIOPin_ WeikaiGPIOPin class
49
- The WeikaiGPIOPin class is an helper class to expose the GPIO pins of WK family components as if they were internal
50
- GPIO pins. It also provides the setup() and dump_summary() methods.
51
-
52
- @section WeikaiChannel_ The WeikaiChannel class
53
- The WeikaiChannel class is used to implement all the virtual methods of the ESPHome uart::UARTComponent class. An
54
- individual instance of this class is created for each UART channel. It has a link back to the WeikaiComponent object it
55
- belongs to. This class derives from the uart::UARTComponent class. It collaborates through an aggregation with
56
- WeikaiComponent. This implies that WeikaiComponent acts as a container, housing several WeikaiChannel instances.
57
- Furthermore, the WeikaiChannel class derives from the ESPHome uart::UARTComponent class, it also has an association
58
- relationship with the WKRingBuffer and WeikaiRegister helper classes. Consequently, when a WeikaiChannel instance is
59
- destroyed, the associated WKRingBuffer instance is also destroyed.
60
-
61
- */
62
-
63
11
  static const char *const TAG = "weikai";
64
12
 
65
13
  /// @brief convert an int to binary representation as C++ std::string
@@ -15,7 +15,7 @@ MODELS = {
15
15
  "DG11J1-91": Model.MODEL_DG11J1_91,
16
16
  }
17
17
 
18
- CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(WhirlpoolClimate).extend(
18
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(WhirlpoolClimate).extend(
19
19
  {
20
20
  cv.Optional(CONF_MODEL, default="DG11J1-3A"): cv.enum(MODELS, upper=True),
21
21
  }
@@ -9,7 +9,7 @@ whynter_ns = cg.esphome_ns.namespace("whynter")
9
9
  Whynter = whynter_ns.class_("Whynter", climate_ir.ClimateIR)
10
10
 
11
11
 
12
- CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(Whynter).extend(
12
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(Whynter).extend(
13
13
  {
14
14
  cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
15
15
  }
@@ -7,7 +7,7 @@ CODEOWNERS = ["@cfeenstra1024"]
7
7
  zhlt01_ns = cg.esphome_ns.namespace("zhlt01")
8
8
  ZHLT01Climate = zhlt01_ns.class_("ZHLT01Climate", climate_ir.ClimateIR)
9
9
 
10
- CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(ZHLT01Climate)
10
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(ZHLT01Climate)
11
11
 
12
12
 
13
13
  async def to_code(config):
esphome/config.py CHANGED
@@ -28,7 +28,7 @@ import esphome.core.config as core_config
28
28
  import esphome.final_validate as fv
29
29
  from esphome.helpers import indent
30
30
  from esphome.loader import ComponentManifest, get_component, get_platform
31
- from esphome.log import Fore, color
31
+ from esphome.log import AnsiFore, color
32
32
  from esphome.types import ConfigFragmentType, ConfigType
33
33
  from esphome.util import OrderedDict, safe_print
34
34
  from esphome.voluptuous_schema import ExtraKeysInvalid
@@ -959,7 +959,7 @@ def line_info(config, path, highlight=True):
959
959
  if obj:
960
960
  mark = obj.start_mark
961
961
  source = f"[source {mark.document}:{mark.line + 1}]"
962
- return color(Fore.CYAN, source)
962
+ return color(AnsiFore.CYAN, source)
963
963
  return "None"
964
964
 
965
965
 
@@ -983,7 +983,7 @@ def dump_dict(
983
983
  if at_root:
984
984
  error = config.get_error_for_path(path)
985
985
  if error is not None:
986
- ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n"
986
+ ret += f"\n{color(AnsiFore.BOLD_RED, _format_vol_invalid(error, config))}\n"
987
987
 
988
988
  if isinstance(conf, (list, tuple)):
989
989
  multiline = True
@@ -995,11 +995,11 @@ def dump_dict(
995
995
  path_ = path + [i]
996
996
  error = config.get_error_for_path(path_)
997
997
  if error is not None:
998
- ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n"
998
+ ret += f"\n{color(AnsiFore.BOLD_RED, _format_vol_invalid(error, config))}\n"
999
999
 
1000
1000
  sep = "- "
1001
1001
  if config.is_in_error_path(path_):
1002
- sep = color(Fore.RED, sep)
1002
+ sep = color(AnsiFore.RED, sep)
1003
1003
  msg, _ = dump_dict(config, path_, at_root=False)
1004
1004
  msg = indent(msg)
1005
1005
  inf = line_info(config, path_, highlight=config.is_in_error_path(path_))
@@ -1018,11 +1018,11 @@ def dump_dict(
1018
1018
  path_ = path + [k]
1019
1019
  error = config.get_error_for_path(path_)
1020
1020
  if error is not None:
1021
- ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n"
1021
+ ret += f"\n{color(AnsiFore.BOLD_RED, _format_vol_invalid(error, config))}\n"
1022
1022
 
1023
1023
  st = f"{k}: "
1024
1024
  if config.is_in_error_path(path_):
1025
- st = color(Fore.RED, st)
1025
+ st = color(AnsiFore.RED, st)
1026
1026
  msg, m = dump_dict(config, path_, at_root=False)
1027
1027
 
1028
1028
  inf = line_info(config, path_, highlight=config.is_in_error_path(path_))
@@ -1044,7 +1044,7 @@ def dump_dict(
1044
1044
  if len(conf) > 80:
1045
1045
  conf = f"|-\n{indent(conf)}"
1046
1046
  error = config.get_error_for_path(path)
1047
- col = Fore.BOLD_RED if error else Fore.KEEP
1047
+ col = AnsiFore.BOLD_RED if error else AnsiFore.KEEP
1048
1048
  ret += color(col, str(conf))
1049
1049
  elif isinstance(conf, core.Lambda):
1050
1050
  if is_secret(conf):
@@ -1052,13 +1052,13 @@ def dump_dict(
1052
1052
 
1053
1053
  conf = f"!lambda |-\n{indent(str(conf.value))}"
1054
1054
  error = config.get_error_for_path(path)
1055
- col = Fore.BOLD_RED if error else Fore.KEEP
1055
+ col = AnsiFore.BOLD_RED if error else AnsiFore.KEEP
1056
1056
  ret += color(col, conf)
1057
1057
  elif conf is None:
1058
1058
  pass
1059
1059
  else:
1060
1060
  error = config.get_error_for_path(path)
1061
- col = Fore.BOLD_RED if error else Fore.KEEP
1061
+ col = AnsiFore.BOLD_RED if error else AnsiFore.KEEP
1062
1062
  ret += color(col, str(conf))
1063
1063
  multiline = "\n" in ret
1064
1064
 
@@ -1100,13 +1100,13 @@ def read_config(command_line_substitutions):
1100
1100
  if not CORE.verbose:
1101
1101
  res = strip_default_ids(res)
1102
1102
 
1103
- safe_print(color(Fore.BOLD_RED, "Failed config"))
1103
+ safe_print(color(AnsiFore.BOLD_RED, "Failed config"))
1104
1104
  safe_print("")
1105
1105
  for path, domain in res.output_paths:
1106
1106
  if not res.is_in_error_path(path):
1107
1107
  continue
1108
1108
 
1109
- errstr = color(Fore.BOLD_RED, f"{domain}:")
1109
+ errstr = color(AnsiFore.BOLD_RED, f"{domain}:")
1110
1110
  errline = line_info(res, path)
1111
1111
  if errline:
1112
1112
  errstr += f" {errline}"
@@ -1121,7 +1121,7 @@ def read_config(command_line_substitutions):
1121
1121
  safe_print(indent("\n".join(split_dump[:i])))
1122
1122
 
1123
1123
  for err in res.errors:
1124
- safe_print(color(Fore.BOLD_RED, err.msg))
1124
+ safe_print(color(AnsiFore.BOLD_RED, err.msg))
1125
1125
  safe_print("")
1126
1126
 
1127
1127
  return None
esphome/const.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Constants used by esphome."""
2
2
 
3
- __version__ = "2025.5.0b2"
3
+ __version__ = "2025.5.0b4"
4
4
 
5
5
  ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
6
6
  VALID_SUBSTITUTIONS_CHARACTERS = (
@@ -35,6 +35,8 @@ void Application::setup() {
35
35
  for (uint32_t i = 0; i < this->components_.size(); i++) {
36
36
  Component *component = this->components_[i];
37
37
 
38
+ // Update loop_component_start_time_ before calling each component during setup
39
+ this->loop_component_start_time_ = millis();
38
40
  component->call();
39
41
  this->scheduler.process_to_add();
40
42
  this->feed_wdt();
@@ -49,6 +51,8 @@ void Application::setup() {
49
51
  this->scheduler.call();
50
52
  this->feed_wdt();
51
53
  for (uint32_t j = 0; j <= i; j++) {
54
+ // Update loop_component_start_time_ right before calling each component
55
+ this->loop_component_start_time_ = millis();
52
56
  this->components_[j]->call();
53
57
  new_app_state |= this->components_[j]->get_component_state();
54
58
  this->app_state_ |= new_app_state;
@@ -67,22 +71,32 @@ void Application::loop() {
67
71
  uint32_t new_app_state = 0;
68
72
 
69
73
  this->scheduler.call();
70
- this->feed_wdt();
74
+
75
+ // Get the initial loop time at the start
76
+ uint32_t last_op_end_time = millis();
77
+
78
+ // Feed WDT with time
79
+ this->feed_wdt(last_op_end_time);
80
+
71
81
  for (Component *component : this->looping_components_) {
82
+ // Update the cached time before each component runs
83
+ this->loop_component_start_time_ = last_op_end_time;
84
+
72
85
  {
73
86
  this->set_current_component(component);
74
- WarnIfComponentBlockingGuard guard{component};
87
+ WarnIfComponentBlockingGuard guard{component, last_op_end_time};
75
88
  component->call();
89
+ // Use the finish method to get the current time as the end time
90
+ last_op_end_time = guard.finish();
76
91
  }
77
92
  new_app_state |= component->get_component_state();
78
93
  this->app_state_ |= new_app_state;
79
- this->feed_wdt();
94
+ this->feed_wdt(last_op_end_time);
80
95
  }
81
96
  this->app_state_ = new_app_state;
82
97
 
83
- const uint32_t now = millis();
84
-
85
- auto elapsed = now - this->last_loop_;
98
+ // Use the last component's end time instead of calling millis() again
99
+ auto elapsed = last_op_end_time - this->last_loop_;
86
100
  if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
87
101
  yield();
88
102
  } else {
@@ -94,7 +108,7 @@ void Application::loop() {
94
108
  delay_time = std::min(next_schedule, delay_time);
95
109
  delay(delay_time);
96
110
  }
97
- this->last_loop_ = now;
111
+ this->last_loop_ = last_op_end_time;
98
112
 
99
113
  if (this->dump_config_at_ < this->components_.size()) {
100
114
  if (this->dump_config_at_ == 0) {
@@ -109,10 +123,12 @@ void Application::loop() {
109
123
  }
110
124
  }
111
125
 
112
- void IRAM_ATTR HOT Application::feed_wdt() {
126
+ void IRAM_ATTR HOT Application::feed_wdt(uint32_t time) {
113
127
  static uint32_t last_feed = 0;
114
- uint32_t now = micros();
115
- if (now - last_feed > 3000) {
128
+ // Use provided time if available, otherwise get current time
129
+ uint32_t now = time ? time : millis();
130
+ // Compare in milliseconds (3ms threshold)
131
+ if (now - last_feed > 3) {
116
132
  arch_feed_wdt();
117
133
  last_feed = now;
118
134
  #ifdef USE_STATUS_LED
@@ -217,6 +217,9 @@ class Application {
217
217
 
218
218
  std::string get_compilation_time() const { return this->compilation_time_; }
219
219
 
220
+ /// Get the cached time in milliseconds from when the current component started its loop execution
221
+ inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; }
222
+
220
223
  /** Set the target interval with which to run the loop() calls.
221
224
  * If the loop() method takes longer than the target interval, ESPHome won't
222
225
  * sleep in loop(), but if the time spent in loop() is small than the target, ESPHome
@@ -236,7 +239,7 @@ class Application {
236
239
 
237
240
  void schedule_dump_config() { this->dump_config_at_ = 0; }
238
241
 
239
- void feed_wdt();
242
+ void feed_wdt(uint32_t time = 0);
240
243
 
241
244
  void reboot();
242
245
 
@@ -551,6 +554,7 @@ class Application {
551
554
  size_t dump_config_at_{SIZE_MAX};
552
555
  uint32_t app_state_{0};
553
556
  Component *current_component_{nullptr};
557
+ uint32_t loop_component_start_time_{0};
554
558
  };
555
559
 
556
560
  /// Global storage of Application pointer - only one Application can exist.
@@ -240,10 +240,12 @@ void PollingComponent::stop_poller() {
240
240
  uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
241
241
  void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
242
242
 
243
- WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component)
244
- : started_(millis()), component_(component) {}
245
- WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
246
- uint32_t blocking_time = millis() - this->started_;
243
+ WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component, uint32_t start_time)
244
+ : started_(start_time), component_(component) {}
245
+ uint32_t WarnIfComponentBlockingGuard::finish() {
246
+ uint32_t curr_time = millis();
247
+
248
+ uint32_t blocking_time = curr_time - this->started_;
247
249
  bool should_warn;
248
250
  if (this->component_ != nullptr) {
249
251
  should_warn = this->component_->should_warn_of_blocking(blocking_time);
@@ -254,8 +256,11 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
254
256
  const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
255
257
  ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, blocking_time);
256
258
  ESP_LOGW(TAG, "Components should block for at most 30 ms.");
257
- ;
258
259
  }
260
+
261
+ return curr_time;
259
262
  }
260
263
 
264
+ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {}
265
+
261
266
  } // namespace esphome
esphome/core/component.h CHANGED
@@ -339,7 +339,11 @@ class PollingComponent : public Component {
339
339
 
340
340
  class WarnIfComponentBlockingGuard {
341
341
  public:
342
- WarnIfComponentBlockingGuard(Component *component);
342
+ WarnIfComponentBlockingGuard(Component *component, uint32_t start_time);
343
+
344
+ // Finish the timing operation and return the current time
345
+ uint32_t finish();
346
+
343
347
  ~WarnIfComponentBlockingGuard();
344
348
 
345
349
  protected:
esphome/core/doxygen.h ADDED
@@ -0,0 +1,13 @@
1
+ #pragma once
2
+
3
+ namespace esphome {
4
+
5
+ /*! @mainpage ESPHome source code documentation
6
+ This documentation provides references to the ESPHome source code classes and methods.
7
+
8
+ @details This documentation site is purely for reference and does not contain any user documentation.
9
+ If you are contributing to ESPHome or building an ESPHome component, then you should be starting at
10
+ https://developers.esphome.io.
11
+ */
12
+
13
+ } // namespace esphome
@@ -229,8 +229,11 @@ void HOT Scheduler::call() {
229
229
  // - timeouts/intervals get added, potentially invalidating vector pointers
230
230
  // - timeouts/intervals get cancelled
231
231
  {
232
- WarnIfComponentBlockingGuard guard{item->component};
232
+ uint32_t now_ms = millis();
233
+ WarnIfComponentBlockingGuard guard{item->component, now_ms};
233
234
  item->callback();
235
+ // Call finish to ensure blocking time is properly calculated and reported
236
+ guard.finish();
234
237
  }
235
238
  }
236
239
 
esphome/log.py CHANGED
@@ -1,9 +1,10 @@
1
+ from enum import Enum
1
2
  import logging
2
3
 
3
4
  from esphome.core import CORE
4
5
 
5
6
 
6
- class AnsiFore:
7
+ class AnsiFore(Enum):
7
8
  KEEP = ""
8
9
  BLACK = "\033[30m"
9
10
  RED = "\033[31m"
@@ -26,7 +27,7 @@ class AnsiFore:
26
27
  BOLD_RESET = "\033[1;39m"
27
28
 
28
29
 
29
- class AnsiStyle:
30
+ class AnsiStyle(Enum):
30
31
  BRIGHT = "\033[1m"
31
32
  BOLD = "\033[1m"
32
33
  DIM = "\033[2m"
@@ -35,16 +36,10 @@ class AnsiStyle:
35
36
  RESET_ALL = "\033[0m"
36
37
 
37
38
 
38
- Fore = AnsiFore()
39
- Style = AnsiStyle()
40
-
41
-
42
- def color(col: str, msg: str, reset: bool = True) -> bool:
43
- if col and not col.startswith("\033["):
44
- raise ValueError("Color must be value from esphome.log.Fore")
45
- s = str(col) + msg
39
+ def color(col: AnsiFore, msg: str, reset: bool = True) -> str:
40
+ s = col.value + msg
46
41
  if reset and col:
47
- s += str(Style.RESET_ALL)
42
+ s += AnsiStyle.RESET_ALL.value
48
43
  return s
49
44
 
50
45
 
@@ -54,20 +49,21 @@ class ESPHomeLogFormatter(logging.Formatter):
54
49
  fmt += "%(levelname)s %(message)s"
55
50
  super().__init__(fmt=fmt, style="%")
56
51
 
57
- def format(self, record):
52
+ # @override
53
+ def format(self, record: logging.LogRecord) -> str:
58
54
  formatted = super().format(record)
59
55
  prefix = {
60
- "DEBUG": Fore.CYAN,
61
- "INFO": Fore.GREEN,
62
- "WARNING": Fore.YELLOW,
63
- "ERROR": Fore.RED,
64
- "CRITICAL": Fore.RED,
56
+ "DEBUG": AnsiFore.CYAN.value,
57
+ "INFO": AnsiFore.GREEN.value,
58
+ "WARNING": AnsiFore.YELLOW.value,
59
+ "ERROR": AnsiFore.RED.value,
60
+ "CRITICAL": AnsiFore.RED.value,
65
61
  }.get(record.levelname, "")
66
- return f"{prefix}{formatted}{Style.RESET_ALL}"
62
+ return f"{prefix}{formatted}{AnsiStyle.RESET_ALL.value}"
67
63
 
68
64
 
69
65
  def setup_log(
70
- log_level=logging.INFO,
66
+ log_level: int = logging.INFO,
71
67
  include_timestamp: bool = False,
72
68
  ) -> None:
73
69
  import colorama
esphome/mqtt.py CHANGED
@@ -28,7 +28,7 @@ from esphome.const import (
28
28
  )
29
29
  from esphome.core import CORE, EsphomeError
30
30
  from esphome.helpers import get_int_env, get_str_env
31
- from esphome.log import Fore, color
31
+ from esphome.log import AnsiFore, color
32
32
  from esphome.util import safe_print
33
33
 
34
34
  _LOGGER = logging.getLogger(__name__)
@@ -291,7 +291,7 @@ def get_fingerprint(config):
291
291
 
292
292
  sha1 = hashlib.sha1(cert_der).hexdigest()
293
293
 
294
- safe_print(f"SHA1 Fingerprint: {color(Fore.CYAN, sha1)}")
294
+ safe_print(f"SHA1 Fingerprint: {color(AnsiFore.CYAN, sha1)}")
295
295
  safe_print(
296
296
  f"Copy the string above into mqtt.ssl_fingerprints section of {CORE.config_path}"
297
297
  )
@@ -15,7 +15,9 @@ class ExtraKeysInvalid(vol.Invalid):
15
15
  def ensure_multiple_invalid(err):
16
16
  if isinstance(err, vol.MultipleInvalid):
17
17
  return err
18
- return vol.MultipleInvalid(err)
18
+ if isinstance(err, list):
19
+ return vol.MultipleInvalid(err)
20
+ return vol.MultipleInvalid([err])
19
21
 
20
22
 
21
23
  # pylint: disable=protected-access, unidiomatic-typecheck