esphome 2025.5.0b2__py3-none-any.whl → 2025.5.0b3__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 (101) hide show
  1. esphome/__main__.py +16 -14
  2. esphome/components/api/api_connection.cpp +4 -3
  3. esphome/components/api/api_connection.h +8 -1
  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/esp32_camera.cpp +2 -1
  33. esphome/components/esp32_camera/esp32_camera.h +1 -1
  34. esphome/components/esp32_improv/esp32_improv_component.cpp +1 -1
  35. esphome/components/esp32_touch/esp32_touch.cpp +1 -1
  36. esphome/components/ethernet/ethernet_component.cpp +1 -1
  37. esphome/components/feedback/feedback_cover.cpp +2 -1
  38. esphome/components/fujitsu_general/climate.py +1 -1
  39. esphome/components/gcja5/gcja5.cpp +2 -1
  40. esphome/components/gps/__init__.py +37 -16
  41. esphome/components/gps/gps.cpp +33 -17
  42. esphome/components/gps/gps.h +16 -15
  43. esphome/components/gree/climate.py +1 -1
  44. esphome/components/growatt_solar/growatt_solar.cpp +2 -1
  45. esphome/components/heatpumpir/climate.py +1 -1
  46. esphome/components/hitachi_ac344/climate.py +1 -1
  47. esphome/components/hitachi_ac424/climate.py +1 -1
  48. esphome/components/hte501/sensor.py +6 -6
  49. esphome/components/hyt271/sensor.py +6 -6
  50. esphome/components/kuntze/kuntze.cpp +2 -1
  51. esphome/components/logger/__init__.py +1 -0
  52. esphome/components/logger/logger.cpp +53 -32
  53. esphome/components/logger/logger.h +55 -5
  54. esphome/components/matrix_keypad/matrix_keypad.cpp +2 -1
  55. esphome/components/max7219digit/max7219digit.cpp +2 -1
  56. esphome/components/mhz19/sensor.py +11 -7
  57. esphome/components/midea_ir/climate.py +1 -1
  58. esphome/components/mitsubishi/climate.py +1 -1
  59. esphome/components/modbus/modbus.cpp +2 -1
  60. esphome/components/mqtt/mqtt_client.cpp +1 -1
  61. esphome/components/ms5611/sensor.py +6 -6
  62. esphome/components/ms8607/sensor.py +3 -3
  63. esphome/components/noblex/climate.py +1 -1
  64. esphome/components/pmsx003/pmsx003.cpp +2 -1
  65. esphome/components/pzem004t/pzem004t.cpp +2 -1
  66. esphome/components/rf_bridge/rf_bridge.cpp +2 -1
  67. esphome/components/sds011/sds011.cpp +2 -1
  68. esphome/components/sen5x/sen5x.cpp +55 -36
  69. esphome/components/senseair/sensor.py +3 -3
  70. esphome/components/sgp30/sensor.py +14 -16
  71. esphome/components/shtcx/sensor.py +6 -6
  72. esphome/components/slow_pwm/slow_pwm_output.cpp +2 -1
  73. esphome/components/sprinkler/sprinkler.cpp +6 -5
  74. esphome/components/t6615/sensor.py +3 -3
  75. esphome/components/t6615/t6615.cpp +2 -1
  76. esphome/components/tcl112/climate.py +1 -1
  77. esphome/components/time_based/time_based_cover.cpp +2 -1
  78. esphome/components/toshiba/climate.py +1 -1
  79. esphome/components/uart/switch/uart_switch.cpp +2 -1
  80. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +2 -1
  81. esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
  82. esphome/components/whirlpool/climate.py +1 -1
  83. esphome/components/whynter/climate.py +1 -1
  84. esphome/components/zhlt01/climate.py +1 -1
  85. esphome/config.py +13 -13
  86. esphome/const.py +1 -1
  87. esphome/core/application.cpp +22 -10
  88. esphome/core/application.h +5 -1
  89. esphome/core/component.cpp +10 -5
  90. esphome/core/component.h +5 -1
  91. esphome/core/scheduler.cpp +4 -1
  92. esphome/log.py +15 -19
  93. esphome/mqtt.py +2 -2
  94. esphome/voluptuous_schema.py +3 -1
  95. esphome/wizard.py +45 -35
  96. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/METADATA +1 -1
  97. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/RECORD +101 -101
  98. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/WHEEL +0 -0
  99. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/entry_points.txt +0 -0
  100. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/licenses/LICENSE +0 -0
  101. {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@
3
3
  #include <cstdarg>
4
4
  #include <map>
5
5
  #ifdef USE_ESP32
6
- #include <atomic>
6
+ #include <pthread.h>
7
7
  #endif
8
8
  #include "esphome/core/automation.h"
9
9
  #include "esphome/core/component.h"
@@ -84,6 +84,23 @@ enum UARTSelection {
84
84
  };
85
85
  #endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY
86
86
 
87
+ /**
88
+ * @brief Logger component for all ESPHome logging.
89
+ *
90
+ * This class implements a multi-platform logging system with protection against recursion.
91
+ *
92
+ * Recursion Protection Strategy:
93
+ * - On ESP32: Uses task-specific recursion guards
94
+ * * Main task: Uses a dedicated boolean member variable for efficiency
95
+ * * Other tasks: Uses pthread TLS with a dynamically allocated key for task-specific state
96
+ * - On other platforms: Uses a simple global recursion guard
97
+ *
98
+ * We use pthread TLS via pthread_key_create to create a unique key for storing
99
+ * task-specific recursion state, which:
100
+ * 1. Efficiently handles multiple tasks without locks or mutexes
101
+ * 2. Works with ESP-IDF's pthread implementation that uses a linked list for TLS variables
102
+ * 3. Avoids the limitations of the fixed FreeRTOS task local storage slots
103
+ */
87
104
  class Logger : public Component {
88
105
  public:
89
106
  explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
@@ -102,6 +119,9 @@ class Logger : public Component {
102
119
  #ifdef USE_ESP_IDF
103
120
  uart_port_t get_uart_num() const { return uart_num_; }
104
121
  #endif
122
+ #ifdef USE_ESP32
123
+ void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
124
+ #endif
105
125
  #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
106
126
  void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
107
127
  /// Get the UART used by the logger.
@@ -222,18 +242,22 @@ class Logger : public Component {
222
242
  std::map<std::string, int> log_levels_{};
223
243
  CallbackManager<void(int, const char *, const char *)> log_callback_{};
224
244
  int current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
225
- #ifdef USE_ESP32
226
- std::atomic<bool> recursion_guard_{false};
227
245
  #ifdef USE_ESPHOME_TASK_LOG_BUFFER
228
246
  std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
229
247
  #endif
248
+ #ifdef USE_ESP32
249
+ // Task-specific recursion guards:
250
+ // - Main task uses a dedicated member variable for efficiency
251
+ // - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
252
+ bool main_task_recursion_guard_{false};
253
+ pthread_key_t log_recursion_key_;
230
254
  #else
231
- bool recursion_guard_{false};
255
+ bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
232
256
  #endif
233
- void *main_task_ = nullptr;
234
257
  CallbackManager<void(int)> level_callback_{};
235
258
 
236
259
  #if defined(USE_ESP32) || defined(USE_LIBRETINY)
260
+ void *main_task_ = nullptr; // Only used for thread name identification
237
261
  const char *HOT get_thread_name_() {
238
262
  TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
239
263
  if (current_task == main_task_) {
@@ -248,6 +272,32 @@ class Logger : public Component {
248
272
  }
249
273
  #endif
250
274
 
275
+ #ifdef USE_ESP32
276
+ inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) {
277
+ if (is_main_task) {
278
+ const bool was_recursive = main_task_recursion_guard_;
279
+ main_task_recursion_guard_ = true;
280
+ return was_recursive;
281
+ }
282
+
283
+ intptr_t current = (intptr_t) pthread_getspecific(log_recursion_key_);
284
+ if (current != 0)
285
+ return true;
286
+
287
+ pthread_setspecific(log_recursion_key_, (void *) 1);
288
+ return false;
289
+ }
290
+
291
+ inline void HOT reset_task_log_recursion_(bool is_main_task) {
292
+ if (is_main_task) {
293
+ main_task_recursion_guard_ = false;
294
+ return;
295
+ }
296
+
297
+ pthread_setspecific(log_recursion_key_, (void *) 0);
298
+ }
299
+ #endif
300
+
251
301
  inline void HOT write_header_to_buffer_(int level, const char *tag, int line, const char *thread_name, char *buffer,
252
302
  int *buffer_at, int buffer_size) {
253
303
  // Format header
@@ -1,5 +1,6 @@
1
1
  #include "matrix_keypad.h"
2
2
  #include "esphome/core/log.h"
3
+ #include "esphome/core/application.h"
3
4
 
4
5
  namespace esphome {
5
6
  namespace matrix_keypad {
@@ -28,7 +29,7 @@ void MatrixKeypad::setup() {
28
29
  void MatrixKeypad::loop() {
29
30
  static uint32_t active_start = 0;
30
31
  static int active_key = -1;
31
- uint32_t now = millis();
32
+ uint32_t now = App.get_loop_component_start_time();
32
33
  int key = -1;
33
34
  bool error = false;
34
35
  int pos = 0, row, col;
@@ -2,6 +2,7 @@
2
2
  #include "esphome/core/log.h"
3
3
  #include "esphome/core/helpers.h"
4
4
  #include "esphome/core/hal.h"
5
+ #include "esphome/core/application.h"
5
6
  #include "max7219font.h"
6
7
 
7
8
  #include <algorithm>
@@ -63,7 +64,7 @@ void MAX7219Component::dump_config() {
63
64
  }
64
65
 
65
66
  void MAX7219Component::loop() {
66
- const uint32_t now = millis();
67
+ const uint32_t now = App.get_loop_component_start_time();
67
68
  const uint32_t millis_since_last_scroll = now - this->last_scroll_;
68
69
  const size_t first_line_size = this->max_displaybuffer_[0].size();
69
70
  // check if the buffer has shrunk past the current position since last update
@@ -32,7 +32,7 @@ CONFIG_SCHEMA = (
32
32
  cv.Schema(
33
33
  {
34
34
  cv.GenerateID(): cv.declare_id(MHZ19Component),
35
- cv.Required(CONF_CO2): sensor.sensor_schema(
35
+ cv.Optional(CONF_CO2): sensor.sensor_schema(
36
36
  unit_of_measurement=UNIT_PARTS_PER_MILLION,
37
37
  icon=ICON_MOLECULE_CO2,
38
38
  accuracy_decimals=0,
@@ -61,16 +61,20 @@ async def to_code(config):
61
61
  await cg.register_component(var, config)
62
62
  await uart.register_uart_device(var, config)
63
63
 
64
- if CONF_CO2 in config:
65
- sens = await sensor.new_sensor(config[CONF_CO2])
64
+ if co2 := config.get(CONF_CO2):
65
+ sens = await sensor.new_sensor(co2)
66
66
  cg.add(var.set_co2_sensor(sens))
67
67
 
68
- if CONF_TEMPERATURE in config:
69
- sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
68
+ if temperature := config.get(CONF_TEMPERATURE):
69
+ sens = await sensor.new_sensor(temperature)
70
70
  cg.add(var.set_temperature_sensor(sens))
71
71
 
72
- if CONF_AUTOMATIC_BASELINE_CALIBRATION in config:
73
- cg.add(var.set_abc_enabled(config[CONF_AUTOMATIC_BASELINE_CALIBRATION]))
72
+ if (
73
+ automatic_baseline_calibration := config.get(
74
+ CONF_AUTOMATIC_BASELINE_CALIBRATION
75
+ )
76
+ ) is not None:
77
+ cg.add(var.set_abc_enabled(automatic_baseline_calibration))
74
78
 
75
79
  cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME]))
76
80
 
@@ -10,7 +10,7 @@ midea_ir_ns = cg.esphome_ns.namespace("midea_ir")
10
10
  MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR)
11
11
 
12
12
 
13
- CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(MideaIR).extend(
13
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MideaIR).extend(
14
14
  {
15
15
  cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
16
16
  }
@@ -43,7 +43,7 @@ VERTICAL_DIRECTIONS = {
43
43
  }
44
44
 
45
45
 
46
- CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(MitsubishiClimate).extend(
46
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MitsubishiClimate).extend(
47
47
  {
48
48
  cv.Optional(CONF_SET_FAN_MODE, default="3levels"): cv.enum(SETFANMODE),
49
49
  cv.Optional(CONF_SUPPORTS_DRY, default=False): cv.boolean,
@@ -1,6 +1,7 @@
1
1
  #include "modbus.h"
2
2
  #include "esphome/core/log.h"
3
3
  #include "esphome/core/helpers.h"
4
+ #include "esphome/core/application.h"
4
5
 
5
6
  namespace esphome {
6
7
  namespace modbus {
@@ -13,7 +14,7 @@ void Modbus::setup() {
13
14
  }
14
15
  }
15
16
  void Modbus::loop() {
16
- const uint32_t now = millis();
17
+ const uint32_t now = App.get_loop_component_start_time();
17
18
 
18
19
  while (this->available()) {
19
20
  uint8_t byte;
@@ -345,7 +345,7 @@ void MQTTClientComponent::loop() {
345
345
  this->disconnect_reason_.reset();
346
346
  }
347
347
 
348
- const uint32_t now = millis();
348
+ const uint32_t now = App.get_loop_component_start_time();
349
349
 
350
350
  switch (this->state_) {
351
351
  case MQTT_CLIENT_DISABLED:
@@ -24,13 +24,13 @@ CONFIG_SCHEMA = (
24
24
  cv.Schema(
25
25
  {
26
26
  cv.GenerateID(): cv.declare_id(MS5611Component),
27
- cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
27
+ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
28
28
  unit_of_measurement=UNIT_CELSIUS,
29
29
  accuracy_decimals=1,
30
30
  device_class=DEVICE_CLASS_TEMPERATURE,
31
31
  state_class=STATE_CLASS_MEASUREMENT,
32
32
  ),
33
- cv.Required(CONF_PRESSURE): sensor.sensor_schema(
33
+ cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
34
34
  unit_of_measurement=UNIT_HECTOPASCAL,
35
35
  icon=ICON_GAUGE,
36
36
  accuracy_decimals=1,
@@ -49,10 +49,10 @@ async def to_code(config):
49
49
  await cg.register_component(var, config)
50
50
  await i2c.register_i2c_device(var, config)
51
51
 
52
- if CONF_TEMPERATURE in config:
53
- sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
52
+ if temperature := config.get(CONF_TEMPERATURE):
53
+ sens = await sensor.new_sensor(temperature)
54
54
  cg.add(var.set_temperature_sensor(sens))
55
55
 
56
- if CONF_PRESSURE in config:
57
- sens = await sensor.new_sensor(config[CONF_PRESSURE])
56
+ if pressure := config.get(CONF_PRESSURE):
57
+ sens = await sensor.new_sensor(pressure)
58
58
  cg.add(var.set_pressure_sensor(sens))
@@ -29,19 +29,19 @@ CONFIG_SCHEMA = (
29
29
  cv.Schema(
30
30
  {
31
31
  cv.GenerateID(): cv.declare_id(MS8607Component),
32
- cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
32
+ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
33
33
  unit_of_measurement=UNIT_CELSIUS,
34
34
  accuracy_decimals=2, # Resolution: 0.01
35
35
  device_class=DEVICE_CLASS_TEMPERATURE,
36
36
  state_class=STATE_CLASS_MEASUREMENT,
37
37
  ),
38
- cv.Required(CONF_PRESSURE): sensor.sensor_schema(
38
+ cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
39
39
  unit_of_measurement=UNIT_HECTOPASCAL,
40
40
  accuracy_decimals=2, # Resolution: 0.016
41
41
  device_class=DEVICE_CLASS_PRESSURE,
42
42
  state_class=STATE_CLASS_MEASUREMENT,
43
43
  ),
44
- cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
44
+ cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
45
45
  unit_of_measurement=UNIT_PERCENT,
46
46
  accuracy_decimals=2, # Resolution: 0.04
47
47
  device_class=DEVICE_CLASS_HUMIDITY,
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
6
6
  noblex_ns = cg.esphome_ns.namespace("noblex")
7
7
  NoblexClimate = noblex_ns.class_("NoblexClimate", climate_ir.ClimateIR)
8
8
 
9
- CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(NoblexClimate)
9
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(NoblexClimate)
10
10
 
11
11
 
12
12
  async def to_code(config):
@@ -1,5 +1,6 @@
1
1
  #include "pmsx003.h"
2
2
  #include "esphome/core/log.h"
3
+ #include "esphome/core/application.h"
3
4
 
4
5
  namespace esphome {
5
6
  namespace pmsx003 {
@@ -42,7 +43,7 @@ void PMSX003Component::dump_config() {
42
43
  }
43
44
 
44
45
  void PMSX003Component::loop() {
45
- const uint32_t now = millis();
46
+ const uint32_t now = App.get_loop_component_start_time();
46
47
 
47
48
  // If we update less often than it takes the device to stabilise, spin the fan down
48
49
  // rather than running it constantly. It does take some time to stabilise, so we
@@ -1,5 +1,6 @@
1
1
  #include "pzem004t.h"
2
2
  #include "esphome/core/log.h"
3
+ #include "esphome/core/application.h"
3
4
  #include <cinttypes>
4
5
 
5
6
  namespace esphome {
@@ -16,7 +17,7 @@ void PZEM004T::setup() {
16
17
  }
17
18
 
18
19
  void PZEM004T::loop() {
19
- const uint32_t now = millis();
20
+ const uint32_t now = App.get_loop_component_start_time();
20
21
  if (now - this->last_read_ > 500 && this->available() < 7) {
21
22
  while (this->available())
22
23
  this->read();
@@ -1,5 +1,6 @@
1
1
  #include "rf_bridge.h"
2
2
  #include "esphome/core/log.h"
3
+ #include "esphome/core/application.h"
3
4
  #include <cinttypes>
4
5
  #include <cstring>
5
6
 
@@ -128,7 +129,7 @@ void RFBridgeComponent::write_byte_str_(const std::string &codes) {
128
129
  }
129
130
 
130
131
  void RFBridgeComponent::loop() {
131
- const uint32_t now = millis();
132
+ const uint32_t now = App.get_loop_component_start_time();
132
133
  if (now - this->last_bridge_byte_ > 50) {
133
134
  this->rx_buffer_.clear();
134
135
  this->last_bridge_byte_ = now;
@@ -1,5 +1,6 @@
1
1
  #include "sds011.h"
2
2
  #include "esphome/core/log.h"
3
+ #include "esphome/core/application.h"
3
4
 
4
5
  namespace esphome {
5
6
  namespace sds011 {
@@ -75,7 +76,7 @@ void SDS011Component::dump_config() {
75
76
  }
76
77
 
77
78
  void SDS011Component::loop() {
78
- const uint32_t now = millis();
79
+ const uint32_t now = App.get_loop_component_start_time();
79
80
  if ((now - this->last_transmission_ >= 500) && this->data_index_) {
80
81
  // last transmission too long ago. Reset RX index.
81
82
  ESP_LOGV(TAG, "Last transmission too long ago. Reset RX index.");
@@ -25,6 +25,10 @@ static const uint16_t SEN5X_CMD_TEMPERATURE_COMPENSATION = 0x60B2;
25
25
  static const uint16_t SEN5X_CMD_VOC_ALGORITHM_STATE = 0x6181;
26
26
  static const uint16_t SEN5X_CMD_VOC_ALGORITHM_TUNING = 0x60D0;
27
27
 
28
+ static const int8_t SEN5X_INDEX_SCALE_FACTOR = 10; // used for VOC and NOx index values
29
+ static const int8_t SEN5X_MIN_INDEX_VALUE = 1 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
30
+ static const int16_t SEN5X_MAX_INDEX_VALUE = 500 * SEN5X_INDEX_SCALE_FACTOR; // must be adjusted by the scale factor
31
+
28
32
  void SEN5XComponent::setup() {
29
33
  ESP_LOGCONFIG(TAG, "Setting up sen5x...");
30
34
 
@@ -88,8 +92,9 @@ void SEN5XComponent::setup() {
88
92
  product_name_.push_back(current_char);
89
93
  // second char
90
94
  current_char = *current_int & 0xFF;
91
- if (current_char)
95
+ if (current_char) {
92
96
  product_name_.push_back(current_char);
97
+ }
93
98
  }
94
99
  current_int++;
95
100
  } while (current_char && --max);
@@ -271,10 +276,10 @@ void SEN5XComponent::dump_config() {
271
276
  ESP_LOGCONFIG(TAG, " Low RH/T acceleration mode");
272
277
  break;
273
278
  case MEDIUM_ACCELERATION:
274
- ESP_LOGCONFIG(TAG, " Medium RH/T accelertion mode");
279
+ ESP_LOGCONFIG(TAG, " Medium RH/T acceleration mode");
275
280
  break;
276
281
  case HIGH_ACCELERATION:
277
- ESP_LOGCONFIG(TAG, " High RH/T accelertion mode");
282
+ ESP_LOGCONFIG(TAG, " High RH/T acceleration mode");
278
283
  break;
279
284
  }
280
285
  }
@@ -337,47 +342,61 @@ void SEN5XComponent::update() {
337
342
  ESP_LOGD(TAG, "read data error (%d)", this->last_error_);
338
343
  return;
339
344
  }
340
- float pm_1_0 = measurements[0] / 10.0;
341
- if (measurements[0] == 0xFFFF)
342
- pm_1_0 = NAN;
343
- float pm_2_5 = measurements[1] / 10.0;
344
- if (measurements[1] == 0xFFFF)
345
- pm_2_5 = NAN;
346
- float pm_4_0 = measurements[2] / 10.0;
347
- if (measurements[2] == 0xFFFF)
348
- pm_4_0 = NAN;
349
- float pm_10_0 = measurements[3] / 10.0;
350
- if (measurements[3] == 0xFFFF)
351
- pm_10_0 = NAN;
352
- float humidity = measurements[4] / 100.0;
353
- if (measurements[4] == 0xFFFF)
354
- humidity = NAN;
355
- float temperature = (int16_t) measurements[5] / 200.0;
356
- if (measurements[5] == 0xFFFF)
357
- temperature = NAN;
358
- float voc = measurements[6] / 10.0;
359
- if (measurements[6] == 0xFFFF)
360
- voc = NAN;
361
- float nox = measurements[7] / 10.0;
362
- if (measurements[7] == 0xFFFF)
363
- nox = NAN;
364
-
365
- if (this->pm_1_0_sensor_ != nullptr)
345
+
346
+ ESP_LOGVV(TAG, "pm_1_0 = 0x%.4x", measurements[0]);
347
+ float pm_1_0 = measurements[0] == UINT16_MAX ? NAN : measurements[0] / 10.0f;
348
+
349
+ ESP_LOGVV(TAG, "pm_2_5 = 0x%.4x", measurements[1]);
350
+ float pm_2_5 = measurements[1] == UINT16_MAX ? NAN : measurements[1] / 10.0f;
351
+
352
+ ESP_LOGVV(TAG, "pm_4_0 = 0x%.4x", measurements[2]);
353
+ float pm_4_0 = measurements[2] == UINT16_MAX ? NAN : measurements[2] / 10.0f;
354
+
355
+ ESP_LOGVV(TAG, "pm_10_0 = 0x%.4x", measurements[3]);
356
+ float pm_10_0 = measurements[3] == UINT16_MAX ? NAN : measurements[3] / 10.0f;
357
+
358
+ ESP_LOGVV(TAG, "humidity = 0x%.4x", measurements[4]);
359
+ float humidity = measurements[4] == INT16_MAX ? NAN : static_cast<int16_t>(measurements[4]) / 100.0f;
360
+
361
+ ESP_LOGVV(TAG, "temperature = 0x%.4x", measurements[5]);
362
+ float temperature = measurements[5] == INT16_MAX ? NAN : static_cast<int16_t>(measurements[5]) / 200.0f;
363
+
364
+ ESP_LOGVV(TAG, "voc = 0x%.4x", measurements[6]);
365
+ int16_t voc_idx = static_cast<int16_t>(measurements[6]);
366
+ float voc = (voc_idx < SEN5X_MIN_INDEX_VALUE || voc_idx > SEN5X_MAX_INDEX_VALUE)
367
+ ? NAN
368
+ : static_cast<float>(voc_idx) / 10.0f;
369
+
370
+ ESP_LOGVV(TAG, "nox = 0x%.4x", measurements[7]);
371
+ int16_t nox_idx = static_cast<int16_t>(measurements[7]);
372
+ float nox = (nox_idx < SEN5X_MIN_INDEX_VALUE || nox_idx > SEN5X_MAX_INDEX_VALUE)
373
+ ? NAN
374
+ : static_cast<float>(nox_idx) / 10.0f;
375
+
376
+ if (this->pm_1_0_sensor_ != nullptr) {
366
377
  this->pm_1_0_sensor_->publish_state(pm_1_0);
367
- if (this->pm_2_5_sensor_ != nullptr)
378
+ }
379
+ if (this->pm_2_5_sensor_ != nullptr) {
368
380
  this->pm_2_5_sensor_->publish_state(pm_2_5);
369
- if (this->pm_4_0_sensor_ != nullptr)
381
+ }
382
+ if (this->pm_4_0_sensor_ != nullptr) {
370
383
  this->pm_4_0_sensor_->publish_state(pm_4_0);
371
- if (this->pm_10_0_sensor_ != nullptr)
384
+ }
385
+ if (this->pm_10_0_sensor_ != nullptr) {
372
386
  this->pm_10_0_sensor_->publish_state(pm_10_0);
373
- if (this->temperature_sensor_ != nullptr)
387
+ }
388
+ if (this->temperature_sensor_ != nullptr) {
374
389
  this->temperature_sensor_->publish_state(temperature);
375
- if (this->humidity_sensor_ != nullptr)
390
+ }
391
+ if (this->humidity_sensor_ != nullptr) {
376
392
  this->humidity_sensor_->publish_state(humidity);
377
- if (this->voc_sensor_ != nullptr)
393
+ }
394
+ if (this->voc_sensor_ != nullptr) {
378
395
  this->voc_sensor_->publish_state(voc);
379
- if (this->nox_sensor_ != nullptr)
396
+ }
397
+ if (this->nox_sensor_ != nullptr) {
380
398
  this->nox_sensor_->publish_state(nox);
399
+ }
381
400
  this->status_clear_warning();
382
401
  });
383
402
  }
@@ -38,7 +38,7 @@ CONFIG_SCHEMA = (
38
38
  cv.Schema(
39
39
  {
40
40
  cv.GenerateID(): cv.declare_id(SenseAirComponent),
41
- cv.Required(CONF_CO2): sensor.sensor_schema(
41
+ cv.Optional(CONF_CO2): sensor.sensor_schema(
42
42
  unit_of_measurement=UNIT_PARTS_PER_MILLION,
43
43
  icon=ICON_MOLECULE_CO2,
44
44
  accuracy_decimals=0,
@@ -57,8 +57,8 @@ async def to_code(config):
57
57
  await cg.register_component(var, config)
58
58
  await uart.register_uart_device(var, config)
59
59
 
60
- if CONF_CO2 in config:
61
- sens = await sensor.new_sensor(config[CONF_CO2])
60
+ if co2 := config.get(CONF_CO2):
61
+ sens = await sensor.new_sensor(co2)
62
62
  cg.add(var.set_co2_sensor(sens))
63
63
 
64
64
 
@@ -37,14 +37,14 @@ CONFIG_SCHEMA = (
37
37
  cv.Schema(
38
38
  {
39
39
  cv.GenerateID(): cv.declare_id(SGP30Component),
40
- cv.Required(CONF_ECO2): sensor.sensor_schema(
40
+ cv.Optional(CONF_ECO2): sensor.sensor_schema(
41
41
  unit_of_measurement=UNIT_PARTS_PER_MILLION,
42
42
  icon=ICON_MOLECULE_CO2,
43
43
  accuracy_decimals=0,
44
44
  device_class=DEVICE_CLASS_CARBON_DIOXIDE,
45
45
  state_class=STATE_CLASS_MEASUREMENT,
46
46
  ),
47
- cv.Required(CONF_TVOC): sensor.sensor_schema(
47
+ cv.Optional(CONF_TVOC): sensor.sensor_schema(
48
48
  unit_of_measurement=UNIT_PARTS_PER_BILLION,
49
49
  icon=ICON_RADIATOR,
50
50
  accuracy_decimals=0,
@@ -86,32 +86,30 @@ async def to_code(config):
86
86
  await cg.register_component(var, config)
87
87
  await i2c.register_i2c_device(var, config)
88
88
 
89
- if CONF_ECO2 in config:
90
- sens = await sensor.new_sensor(config[CONF_ECO2])
89
+ if eco2_config := config.get(CONF_ECO2):
90
+ sens = await sensor.new_sensor(eco2_config)
91
91
  cg.add(var.set_eco2_sensor(sens))
92
92
 
93
- if CONF_TVOC in config:
94
- sens = await sensor.new_sensor(config[CONF_TVOC])
93
+ if tvoc_config := config.get(CONF_TVOC):
94
+ sens = await sensor.new_sensor(tvoc_config)
95
95
  cg.add(var.set_tvoc_sensor(sens))
96
96
 
97
- if CONF_ECO2_BASELINE in config:
98
- sens = await sensor.new_sensor(config[CONF_ECO2_BASELINE])
97
+ if eco2_baseline_config := config.get(CONF_ECO2_BASELINE):
98
+ sens = await sensor.new_sensor(eco2_baseline_config)
99
99
  cg.add(var.set_eco2_baseline_sensor(sens))
100
100
 
101
- if CONF_TVOC_BASELINE in config:
102
- sens = await sensor.new_sensor(config[CONF_TVOC_BASELINE])
101
+ if tvoc_baseline_config := config.get(CONF_TVOC_BASELINE):
102
+ sens = await sensor.new_sensor(tvoc_baseline_config)
103
103
  cg.add(var.set_tvoc_baseline_sensor(sens))
104
104
 
105
- if CONF_STORE_BASELINE in config:
106
- cg.add(var.set_store_baseline(config[CONF_STORE_BASELINE]))
105
+ if (store_baseline := config.get(CONF_STORE_BASELINE)) is not None:
106
+ cg.add(var.set_store_baseline(store_baseline))
107
107
 
108
- if CONF_BASELINE in config:
109
- baseline_config = config[CONF_BASELINE]
108
+ if baseline_config := config.get(CONF_BASELINE):
110
109
  cg.add(var.set_eco2_baseline(baseline_config[CONF_ECO2_BASELINE]))
111
110
  cg.add(var.set_tvoc_baseline(baseline_config[CONF_TVOC_BASELINE]))
112
111
 
113
- if CONF_COMPENSATION in config:
114
- compensation_config = config[CONF_COMPENSATION]
112
+ if compensation_config := config.get(CONF_COMPENSATION):
115
113
  sens = await cg.get_variable(compensation_config[CONF_HUMIDITY_SOURCE])
116
114
  cg.add(var.set_humidity_sensor(sens))
117
115
  sens = await cg.get_variable(compensation_config[CONF_TEMPERATURE_SOURCE])
@@ -26,13 +26,13 @@ CONFIG_SCHEMA = (
26
26
  cv.Schema(
27
27
  {
28
28
  cv.GenerateID(): cv.declare_id(SHTCXComponent),
29
- cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
29
+ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
30
30
  unit_of_measurement=UNIT_CELSIUS,
31
31
  accuracy_decimals=1,
32
32
  device_class=DEVICE_CLASS_TEMPERATURE,
33
33
  state_class=STATE_CLASS_MEASUREMENT,
34
34
  ),
35
- cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
35
+ cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
36
36
  unit_of_measurement=UNIT_PERCENT,
37
37
  accuracy_decimals=1,
38
38
  device_class=DEVICE_CLASS_HUMIDITY,
@@ -50,10 +50,10 @@ async def to_code(config):
50
50
  await cg.register_component(var, config)
51
51
  await i2c.register_i2c_device(var, config)
52
52
 
53
- if CONF_TEMPERATURE in config:
54
- sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
53
+ if temperature := config.get(CONF_TEMPERATURE):
54
+ sens = await sensor.new_sensor(temperature)
55
55
  cg.add(var.set_temperature_sensor(sens))
56
56
 
57
- if CONF_HUMIDITY in config:
58
- sens = await sensor.new_sensor(config[CONF_HUMIDITY])
57
+ if humidity := config.get(CONF_HUMIDITY):
58
+ sens = await sensor.new_sensor(humidity)
59
59
  cg.add(var.set_humidity_sensor(sens))
@@ -1,5 +1,6 @@
1
1
  #include "slow_pwm_output.h"
2
2
  #include "esphome/core/log.h"
3
+ #include "esphome/core/application.h"
3
4
 
4
5
  namespace esphome {
5
6
  namespace slow_pwm {
@@ -39,7 +40,7 @@ void SlowPWMOutput::set_output_state_(bool new_state) {
39
40
  }
40
41
 
41
42
  void SlowPWMOutput::loop() {
42
- uint32_t now = millis();
43
+ uint32_t now = App.get_loop_component_start_time();
43
44
  float scaled_state = this->state_ * this->period_;
44
45
 
45
46
  if (now - this->period_start_time_ >= this->period_) {
@@ -20,7 +20,7 @@ SprinklerSwitch::SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *o
20
20
  bool SprinklerSwitch::is_latching_valve() { return (this->off_switch_ != nullptr) && (this->on_switch_ != nullptr); }
21
21
 
22
22
  void SprinklerSwitch::loop() {
23
- if ((this->pinned_millis_) && (millis() > this->pinned_millis_ + this->pulse_duration_)) {
23
+ if ((this->pinned_millis_) && (App.get_loop_component_start_time() > this->pinned_millis_ + this->pulse_duration_)) {
24
24
  this->pinned_millis_ = 0; // reset tracker
25
25
  if (this->off_switch_->state) {
26
26
  this->off_switch_->turn_off();
@@ -148,22 +148,23 @@ SprinklerValveOperator::SprinklerValveOperator(SprinklerValve *valve, Sprinkler
148
148
  : controller_(controller), valve_(valve) {}
149
149
 
150
150
  void SprinklerValveOperator::loop() {
151
- if (millis() >= this->start_millis_) { // dummy check
151
+ uint32_t now = App.get_loop_component_start_time();
152
+ if (now >= this->start_millis_) { // dummy check
152
153
  switch (this->state_) {
153
154
  case STARTING:
154
- if (millis() > (this->start_millis_ + this->start_delay_)) {
155
+ if (now > (this->start_millis_ + this->start_delay_)) {
155
156
  this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
156
157
  }
157
158
  break;
158
159
 
159
160
  case ACTIVE:
160
- if (millis() > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
161
+ if (now > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
161
162
  this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
162
163
  }
163
164
  break;
164
165
 
165
166
  case STOPPING:
166
- if (millis() > (this->stop_millis_ + this->stop_delay_)) {
167
+ if (now > (this->stop_millis_ + this->stop_delay_)) {
167
168
  this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
168
169
  }
169
170
  break;