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
@@ -9,23 +9,32 @@ from esphome.const import (
9
9
  CONF_LONGITUDE,
10
10
  CONF_SATELLITES,
11
11
  CONF_SPEED,
12
+ DEVICE_CLASS_SPEED,
12
13
  STATE_CLASS_MEASUREMENT,
13
14
  UNIT_DEGREES,
14
15
  UNIT_KILOMETER_PER_HOUR,
15
16
  UNIT_METER,
16
17
  )
17
18
 
19
+ CONF_GPS_ID = "gps_id"
20
+ CONF_HDOP = "hdop"
21
+
22
+ ICON_ALTIMETER = "mdi:altimeter"
23
+ ICON_COMPASS = "mdi:compass"
24
+ ICON_LATITUDE = "mdi:latitude"
25
+ ICON_LONGITUDE = "mdi:longitude"
26
+ ICON_SATELLITE = "mdi:satellite-variant"
27
+ ICON_SPEEDOMETER = "mdi:speedometer"
28
+
18
29
  DEPENDENCIES = ["uart"]
19
30
  AUTO_LOAD = ["sensor"]
20
31
 
21
- CODEOWNERS = ["@coogle"]
32
+ CODEOWNERS = ["@coogle", "@ximex"]
22
33
 
23
34
  gps_ns = cg.esphome_ns.namespace("gps")
24
35
  GPS = gps_ns.class_("GPS", cg.Component, uart.UARTDevice)
25
36
  GPSListener = gps_ns.class_("GPSListener")
26
37
 
27
- CONF_GPS_ID = "gps_id"
28
- CONF_HDOP = "hdop"
29
38
  MULTI_CONF = True
30
39
  CONFIG_SCHEMA = cv.All(
31
40
  cv.Schema(
@@ -33,25 +42,37 @@ CONFIG_SCHEMA = cv.All(
33
42
  cv.GenerateID(): cv.declare_id(GPS),
34
43
  cv.Optional(CONF_LATITUDE): sensor.sensor_schema(
35
44
  unit_of_measurement=UNIT_DEGREES,
45
+ icon=ICON_LATITUDE,
36
46
  accuracy_decimals=6,
47
+ state_class=STATE_CLASS_MEASUREMENT,
37
48
  ),
38
49
  cv.Optional(CONF_LONGITUDE): sensor.sensor_schema(
39
50
  unit_of_measurement=UNIT_DEGREES,
51
+ icon=ICON_LONGITUDE,
40
52
  accuracy_decimals=6,
53
+ state_class=STATE_CLASS_MEASUREMENT,
41
54
  ),
42
55
  cv.Optional(CONF_SPEED): sensor.sensor_schema(
43
56
  unit_of_measurement=UNIT_KILOMETER_PER_HOUR,
57
+ icon=ICON_SPEEDOMETER,
44
58
  accuracy_decimals=3,
59
+ device_class=DEVICE_CLASS_SPEED,
60
+ state_class=STATE_CLASS_MEASUREMENT,
45
61
  ),
46
62
  cv.Optional(CONF_COURSE): sensor.sensor_schema(
47
63
  unit_of_measurement=UNIT_DEGREES,
64
+ icon=ICON_COMPASS,
48
65
  accuracy_decimals=2,
66
+ state_class=STATE_CLASS_MEASUREMENT,
49
67
  ),
50
68
  cv.Optional(CONF_ALTITUDE): sensor.sensor_schema(
51
69
  unit_of_measurement=UNIT_METER,
70
+ icon=ICON_ALTIMETER,
52
71
  accuracy_decimals=2,
72
+ state_class=STATE_CLASS_MEASUREMENT,
53
73
  ),
54
74
  cv.Optional(CONF_SATELLITES): sensor.sensor_schema(
75
+ icon=ICON_SATELLITE,
55
76
  accuracy_decimals=0,
56
77
  state_class=STATE_CLASS_MEASUREMENT,
57
78
  ),
@@ -73,28 +94,28 @@ async def to_code(config):
73
94
  await cg.register_component(var, config)
74
95
  await uart.register_uart_device(var, config)
75
96
 
76
- if CONF_LATITUDE in config:
77
- sens = await sensor.new_sensor(config[CONF_LATITUDE])
97
+ if latitude_config := config.get(CONF_LATITUDE):
98
+ sens = await sensor.new_sensor(latitude_config)
78
99
  cg.add(var.set_latitude_sensor(sens))
79
100
 
80
- if CONF_LONGITUDE in config:
81
- sens = await sensor.new_sensor(config[CONF_LONGITUDE])
101
+ if longitude_config := config.get(CONF_LONGITUDE):
102
+ sens = await sensor.new_sensor(longitude_config)
82
103
  cg.add(var.set_longitude_sensor(sens))
83
104
 
84
- if CONF_SPEED in config:
85
- sens = await sensor.new_sensor(config[CONF_SPEED])
105
+ if speed_config := config.get(CONF_SPEED):
106
+ sens = await sensor.new_sensor(speed_config)
86
107
  cg.add(var.set_speed_sensor(sens))
87
108
 
88
- if CONF_COURSE in config:
89
- sens = await sensor.new_sensor(config[CONF_COURSE])
109
+ if course_config := config.get(CONF_COURSE):
110
+ sens = await sensor.new_sensor(course_config)
90
111
  cg.add(var.set_course_sensor(sens))
91
112
 
92
- if CONF_ALTITUDE in config:
93
- sens = await sensor.new_sensor(config[CONF_ALTITUDE])
113
+ if altitude_config := config.get(CONF_ALTITUDE):
114
+ sens = await sensor.new_sensor(altitude_config)
94
115
  cg.add(var.set_altitude_sensor(sens))
95
116
 
96
- if CONF_SATELLITES in config:
97
- sens = await sensor.new_sensor(config[CONF_SATELLITES])
117
+ if satellites_config := config.get(CONF_SATELLITES):
118
+ sens = await sensor.new_sensor(satellites_config)
98
119
  cg.add(var.set_satellites_sensor(sens))
99
120
 
100
121
  if hdop_config := config.get(CONF_HDOP):
@@ -102,4 +123,4 @@ async def to_code(config):
102
123
  cg.add(var.set_hdop_sensor(sens))
103
124
 
104
125
  # https://platformio.org/lib/show/1655/TinyGPSPlus
105
- cg.add_library("mikalhart/TinyGPSPlus", "1.0.2")
126
+ cg.add_library("mikalhart/TinyGPSPlus", "1.1.0")
@@ -10,6 +10,17 @@ static const char *const TAG = "gps";
10
10
 
11
11
  TinyGPSPlus &GPSListener::get_tiny_gps() { return this->parent_->get_tiny_gps(); }
12
12
 
13
+ void GPS::dump_config() {
14
+ ESP_LOGCONFIG(TAG, "GPS:");
15
+ LOG_SENSOR(" ", "Latitude", this->latitude_sensor_);
16
+ LOG_SENSOR(" ", "Longitude", this->longitude_sensor_);
17
+ LOG_SENSOR(" ", "Speed", this->speed_sensor_);
18
+ LOG_SENSOR(" ", "Course", this->course_sensor_);
19
+ LOG_SENSOR(" ", "Altitude", this->altitude_sensor_);
20
+ LOG_SENSOR(" ", "Satellites", this->satellites_sensor_);
21
+ LOG_SENSOR(" ", "HDOP", this->hdop_sensor_);
22
+ }
23
+
13
24
  void GPS::update() {
14
25
  if (this->latitude_sensor_ != nullptr)
15
26
  this->latitude_sensor_->publish_state(this->latitude_);
@@ -34,40 +45,45 @@ void GPS::update() {
34
45
  }
35
46
 
36
47
  void GPS::loop() {
37
- while (this->available() && !this->has_time_) {
48
+ while (this->available() > 0 && !this->has_time_) {
38
49
  if (this->tiny_gps_.encode(this->read())) {
39
- if (tiny_gps_.location.isUpdated()) {
40
- this->latitude_ = tiny_gps_.location.lat();
41
- this->longitude_ = tiny_gps_.location.lng();
50
+ if (this->tiny_gps_.location.isUpdated()) {
51
+ this->latitude_ = this->tiny_gps_.location.lat();
52
+ this->longitude_ = this->tiny_gps_.location.lng();
42
53
 
43
54
  ESP_LOGD(TAG, "Location:");
44
- ESP_LOGD(TAG, " Lat: %f", this->latitude_);
45
- ESP_LOGD(TAG, " Lon: %f", this->longitude_);
55
+ ESP_LOGD(TAG, " Lat: %.6f °", this->latitude_);
56
+ ESP_LOGD(TAG, " Lon: %.6f °", this->longitude_);
46
57
  }
47
58
 
48
- if (tiny_gps_.speed.isUpdated()) {
49
- this->speed_ = tiny_gps_.speed.kmph();
59
+ if (this->tiny_gps_.speed.isUpdated()) {
60
+ this->speed_ = this->tiny_gps_.speed.kmph();
50
61
  ESP_LOGD(TAG, "Speed: %.3f km/h", this->speed_);
51
62
  }
52
- if (tiny_gps_.course.isUpdated()) {
53
- this->course_ = tiny_gps_.course.deg();
63
+
64
+ if (this->tiny_gps_.course.isUpdated()) {
65
+ this->course_ = this->tiny_gps_.course.deg();
54
66
  ESP_LOGD(TAG, "Course: %.2f °", this->course_);
55
67
  }
56
- if (tiny_gps_.altitude.isUpdated()) {
57
- this->altitude_ = tiny_gps_.altitude.meters();
68
+
69
+ if (this->tiny_gps_.altitude.isUpdated()) {
70
+ this->altitude_ = this->tiny_gps_.altitude.meters();
58
71
  ESP_LOGD(TAG, "Altitude: %.2f m", this->altitude_);
59
72
  }
60
- if (tiny_gps_.satellites.isUpdated()) {
61
- this->satellites_ = tiny_gps_.satellites.value();
73
+
74
+ if (this->tiny_gps_.satellites.isUpdated()) {
75
+ this->satellites_ = this->tiny_gps_.satellites.value();
62
76
  ESP_LOGD(TAG, "Satellites: %d", this->satellites_);
63
77
  }
64
- if (tiny_gps_.hdop.isUpdated()) {
65
- this->hdop_ = tiny_gps_.hdop.hdop();
78
+
79
+ if (this->tiny_gps_.hdop.isUpdated()) {
80
+ this->hdop_ = this->tiny_gps_.hdop.hdop();
66
81
  ESP_LOGD(TAG, "HDOP: %.3f", this->hdop_);
67
82
  }
68
83
 
69
- for (auto *listener : this->listeners_)
84
+ for (auto *listener : this->listeners_) {
70
85
  listener->on_update(this->tiny_gps_);
86
+ }
71
87
  }
72
88
  }
73
89
  }
@@ -5,7 +5,7 @@
5
5
  #include "esphome/core/component.h"
6
6
  #include "esphome/components/uart/uart.h"
7
7
  #include "esphome/components/sensor/sensor.h"
8
- #include <TinyGPS++.h>
8
+ #include <TinyGPSPlus.h>
9
9
 
10
10
  #include <vector>
11
11
 
@@ -27,13 +27,13 @@ class GPSListener {
27
27
 
28
28
  class GPS : public PollingComponent, public uart::UARTDevice {
29
29
  public:
30
- void set_latitude_sensor(sensor::Sensor *latitude_sensor) { latitude_sensor_ = latitude_sensor; }
31
- void set_longitude_sensor(sensor::Sensor *longitude_sensor) { longitude_sensor_ = longitude_sensor; }
32
- void set_speed_sensor(sensor::Sensor *speed_sensor) { speed_sensor_ = speed_sensor; }
33
- void set_course_sensor(sensor::Sensor *course_sensor) { course_sensor_ = course_sensor; }
34
- void set_altitude_sensor(sensor::Sensor *altitude_sensor) { altitude_sensor_ = altitude_sensor; }
35
- void set_satellites_sensor(sensor::Sensor *satellites_sensor) { satellites_sensor_ = satellites_sensor; }
36
- void set_hdop_sensor(sensor::Sensor *hdop_sensor) { hdop_sensor_ = hdop_sensor; }
30
+ void set_latitude_sensor(sensor::Sensor *latitude_sensor) { this->latitude_sensor_ = latitude_sensor; }
31
+ void set_longitude_sensor(sensor::Sensor *longitude_sensor) { this->longitude_sensor_ = longitude_sensor; }
32
+ void set_speed_sensor(sensor::Sensor *speed_sensor) { this->speed_sensor_ = speed_sensor; }
33
+ void set_course_sensor(sensor::Sensor *course_sensor) { this->course_sensor_ = course_sensor; }
34
+ void set_altitude_sensor(sensor::Sensor *altitude_sensor) { this->altitude_sensor_ = altitude_sensor; }
35
+ void set_satellites_sensor(sensor::Sensor *satellites_sensor) { this->satellites_sensor_ = satellites_sensor; }
36
+ void set_hdop_sensor(sensor::Sensor *hdop_sensor) { this->hdop_sensor_ = hdop_sensor; }
37
37
 
38
38
  void register_listener(GPSListener *listener) {
39
39
  listener->parent_ = this;
@@ -41,19 +41,20 @@ class GPS : public PollingComponent, public uart::UARTDevice {
41
41
  }
42
42
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
43
43
 
44
+ void dump_config() override;
44
45
  void loop() override;
45
46
  void update() override;
46
47
 
47
48
  TinyGPSPlus &get_tiny_gps() { return this->tiny_gps_; }
48
49
 
49
50
  protected:
50
- float latitude_ = NAN;
51
- float longitude_ = NAN;
52
- float speed_ = NAN;
53
- float course_ = NAN;
54
- float altitude_ = NAN;
55
- int satellites_ = 0;
56
- double hdop_ = NAN;
51
+ float latitude_{NAN};
52
+ float longitude_{NAN};
53
+ float speed_{NAN};
54
+ float course_{NAN};
55
+ float altitude_{NAN};
56
+ uint16_t satellites_{0};
57
+ float hdop_{NAN};
57
58
 
58
59
  sensor::Sensor *latitude_sensor_{nullptr};
59
60
  sensor::Sensor *longitude_sensor_{nullptr};
@@ -21,7 +21,7 @@ MODELS = {
21
21
  "yag": Model.GREE_YAG,
22
22
  }
23
23
 
24
- CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(GreeClimate).extend(
24
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(GreeClimate).extend(
25
25
  {
26
26
  cv.Required(CONF_MODEL): cv.enum(MODELS),
27
27
  }
@@ -1,5 +1,6 @@
1
1
  #include "growatt_solar.h"
2
2
  #include "esphome/core/log.h"
3
+ #include "esphome/core/application.h"
3
4
 
4
5
  namespace esphome {
5
6
  namespace growatt_solar {
@@ -18,7 +19,7 @@ void GrowattSolar::loop() {
18
19
 
19
20
  void GrowattSolar::update() {
20
21
  // If our last send has had no reply yet, and it wasn't that long ago, do nothing.
21
- uint32_t now = millis();
22
+ const uint32_t now = App.get_loop_component_start_time();
22
23
  if (now - this->last_send_ < this->get_update_interval() / 2) {
23
24
  return;
24
25
  }
@@ -97,7 +97,7 @@ VERTICAL_DIRECTIONS = {
97
97
  }
98
98
 
99
99
  CONFIG_SCHEMA = cv.All(
100
- climate_ir.climare_ir_with_receiver_schema(HeatpumpIRClimate).extend(
100
+ climate_ir.climate_ir_with_receiver_schema(HeatpumpIRClimate).extend(
101
101
  {
102
102
  cv.Required(CONF_PROTOCOL): cv.enum(PROTOCOLS),
103
103
  cv.Required(CONF_HORIZONTAL_DEFAULT): cv.enum(HORIZONTAL_DIRECTIONS),
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
6
6
  hitachi_ac344_ns = cg.esphome_ns.namespace("hitachi_ac344")
7
7
  HitachiClimate = hitachi_ac344_ns.class_("HitachiClimate", climate_ir.ClimateIR)
8
8
 
9
- CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate)
9
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(HitachiClimate)
10
10
 
11
11
 
12
12
  async def to_code(config):
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
6
6
  hitachi_ac424_ns = cg.esphome_ns.namespace("hitachi_ac424")
7
7
  HitachiClimate = hitachi_ac424_ns.class_("HitachiClimate", climate_ir.ClimateIR)
8
8
 
9
- CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate)
9
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(HitachiClimate)
10
10
 
11
11
 
12
12
  async def to_code(config):
@@ -25,13 +25,13 @@ CONFIG_SCHEMA = (
25
25
  cv.Schema(
26
26
  {
27
27
  cv.GenerateID(): cv.declare_id(HTE501Component),
28
- cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
28
+ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
29
29
  unit_of_measurement=UNIT_CELSIUS,
30
30
  accuracy_decimals=1,
31
31
  device_class=DEVICE_CLASS_TEMPERATURE,
32
32
  state_class=STATE_CLASS_MEASUREMENT,
33
33
  ),
34
- cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
34
+ cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
35
35
  unit_of_measurement=UNIT_PERCENT,
36
36
  accuracy_decimals=1,
37
37
  device_class=DEVICE_CLASS_HUMIDITY,
@@ -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_HUMIDITY in config:
57
- sens = await sensor.new_sensor(config[CONF_HUMIDITY])
56
+ if humidity := config.get(CONF_HUMIDITY):
57
+ sens = await sensor.new_sensor(humidity)
58
58
  cg.add(var.set_humidity_sensor(sens))
@@ -23,13 +23,13 @@ CONFIG_SCHEMA = (
23
23
  cv.Schema(
24
24
  {
25
25
  cv.GenerateID(): cv.declare_id(HYT271Component),
26
- cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
26
+ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
27
27
  unit_of_measurement=UNIT_CELSIUS,
28
28
  accuracy_decimals=1,
29
29
  device_class=DEVICE_CLASS_TEMPERATURE,
30
30
  state_class=STATE_CLASS_MEASUREMENT,
31
31
  ),
32
- cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
32
+ cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
33
33
  unit_of_measurement=UNIT_PERCENT,
34
34
  accuracy_decimals=1,
35
35
  device_class=DEVICE_CLASS_HUMIDITY,
@@ -47,10 +47,10 @@ async def to_code(config):
47
47
  await cg.register_component(var, config)
48
48
  await i2c.register_i2c_device(var, config)
49
49
 
50
- if CONF_TEMPERATURE in config:
51
- sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
50
+ if temperature := config.get(CONF_TEMPERATURE):
51
+ sens = await sensor.new_sensor(temperature)
52
52
  cg.add(var.set_temperature(sens))
53
53
 
54
- if CONF_HUMIDITY in config:
55
- sens = await sensor.new_sensor(config[CONF_HUMIDITY])
54
+ if humidity := config.get(CONF_HUMIDITY):
55
+ sens = await sensor.new_sensor(humidity)
56
56
  cg.add(var.set_humidity(sens))
@@ -1,5 +1,6 @@
1
1
  #include "kuntze.h"
2
2
  #include "esphome/core/log.h"
3
+ #include "esphome/core/application.h"
3
4
 
4
5
  namespace esphome {
5
6
  namespace kuntze {
@@ -60,7 +61,7 @@ void Kuntze::on_modbus_data(const std::vector<uint8_t> &data) {
60
61
  }
61
62
 
62
63
  void Kuntze::loop() {
63
- uint32_t now = millis();
64
+ uint32_t now = App.get_loop_component_start_time();
64
65
  // timeout after 15 seconds
65
66
  if (this->waiting_ && (now - this->last_send_ > 15000)) {
66
67
  ESP_LOGW(TAG, "timed out waiting for response");
@@ -254,6 +254,7 @@ async def to_code(config):
254
254
  config[CONF_TX_BUFFER_SIZE],
255
255
  )
256
256
  if CORE.is_esp32:
257
+ cg.add(log.create_pthread_key())
257
258
  task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
258
259
  if task_log_buffer_size > 0:
259
260
  cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
@@ -14,25 +14,47 @@ namespace logger {
14
14
  static const char *const TAG = "logger";
15
15
 
16
16
  #ifdef USE_ESP32
17
- // Implementation for ESP32 (multi-core with atomic support)
18
- // Main thread: synchronous logging with direct buffer access
19
- // Other threads: console output with stack buffer, callbacks via async buffer
17
+ // Implementation for ESP32 (multi-task platform with task-specific tracking)
18
+ // Main task always uses direct buffer access for console output and callbacks
19
+ //
20
+ // For non-main tasks:
21
+ // - WITH task log buffer: Prefer sending to ring buffer for async processing
22
+ // - Avoids allocating stack memory for console output in normal operation
23
+ // - Prevents console corruption from concurrent writes by multiple tasks
24
+ // - Messages are serialized through main loop for proper console output
25
+ // - Fallback to emergency console logging only if ring buffer is full
26
+ // - WITHOUT task log buffer: Only emergency console output, no callbacks
20
27
  void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
21
- if (level > this->level_for(tag) || recursion_guard_.load(std::memory_order_relaxed))
28
+ if (level > this->level_for(tag))
22
29
  return;
23
- recursion_guard_.store(true, std::memory_order_relaxed);
24
30
 
25
31
  TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
32
+ bool is_main_task = (current_task == main_task_);
26
33
 
27
- // For main task: call log_message_to_buffer_and_send_ which does console and callback logging
28
- if (current_task == main_task_) {
34
+ // Check and set recursion guard - uses pthread TLS for per-task state
35
+ if (this->check_and_set_task_log_recursion_(is_main_task)) {
36
+ return; // Recursion detected
37
+ }
38
+
39
+ // Main task uses the shared buffer for efficiency
40
+ if (is_main_task) {
29
41
  this->log_message_to_buffer_and_send_(level, tag, line, format, args);
30
- recursion_guard_.store(false, std::memory_order_release);
42
+ this->reset_task_log_recursion_(is_main_task);
31
43
  return;
32
44
  }
33
45
 
34
- // For non-main tasks: use stack-allocated buffer only for console output
35
- if (this->baud_rate_ > 0) { // If logging is enabled, write to console
46
+ bool message_sent = false;
47
+ #ifdef USE_ESPHOME_TASK_LOG_BUFFER
48
+ // For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
49
+ message_sent = this->log_buffer_->send_message_thread_safe(static_cast<uint8_t>(level), tag,
50
+ static_cast<uint16_t>(line), current_task, format, args);
51
+ #endif // USE_ESPHOME_TASK_LOG_BUFFER
52
+
53
+ // Emergency console logging for non-main tasks when ring buffer is full or disabled
54
+ // This is a fallback mechanism to ensure critical log messages are visible
55
+ // Note: This may cause interleaved/corrupted console output if multiple tasks
56
+ // log simultaneously, but it's better than losing important messages entirely
57
+ if (!message_sent && this->baud_rate_ > 0) { // If logging is enabled, write to console
36
58
  // Maximum size for console log messages (includes null terminator)
37
59
  static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
38
60
  char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
@@ -42,32 +64,21 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
42
64
  this->write_msg_(console_buffer);
43
65
  }
44
66
 
45
- #ifdef USE_ESPHOME_TASK_LOG_BUFFER
46
- // For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
47
- if (this->log_callback_.size() > 0) {
48
- // This will be processed in the main loop
49
- this->log_buffer_->send_message_thread_safe(static_cast<uint8_t>(level), tag, static_cast<uint16_t>(line),
50
- current_task, format, args);
51
- }
52
- #endif // USE_ESPHOME_TASK_LOG_BUFFER
53
-
54
- recursion_guard_.store(false, std::memory_order_release);
67
+ // Reset the recursion guard for this task
68
+ this->reset_task_log_recursion_(is_main_task);
55
69
  }
56
- #endif // USE_ESP32
57
-
58
- #ifndef USE_ESP32
59
- // Implementation for platforms that do not support atomic operations
60
- // or have to consider logging in other tasks
70
+ #else
71
+ // Implementation for all other platforms
61
72
  void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
62
- if (level > this->level_for(tag) || recursion_guard_)
73
+ if (level > this->level_for(tag) || global_recursion_guard_)
63
74
  return;
64
75
 
65
- recursion_guard_ = true;
76
+ global_recursion_guard_ = true;
66
77
 
67
78
  // Format and send to both console and callbacks
68
79
  this->log_message_to_buffer_and_send_(level, tag, line, format, args);
69
80
 
70
- recursion_guard_ = false;
81
+ global_recursion_guard_ = false;
71
82
  }
72
83
  #endif // !USE_ESP32
73
84
 
@@ -76,10 +87,10 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
76
87
  // Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
77
88
  void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format,
78
89
  va_list args) { // NOLINT
79
- if (level > this->level_for(tag) || recursion_guard_)
90
+ if (level > this->level_for(tag) || global_recursion_guard_)
80
91
  return;
81
92
 
82
- recursion_guard_ = true;
93
+ global_recursion_guard_ = true;
83
94
  this->tx_buffer_at_ = 0;
84
95
 
85
96
  // Copy format string from progmem
@@ -91,7 +102,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr
91
102
 
92
103
  // Buffer full from copying format
93
104
  if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
94
- recursion_guard_ = false; // Make sure to reset the recursion guard before returning
105
+ global_recursion_guard_ = false; // Make sure to reset the recursion guard before returning
95
106
  return;
96
107
  }
97
108
 
@@ -107,7 +118,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr
107
118
  }
108
119
  this->call_log_callbacks_(level, tag, this->tx_buffer_ + msg_start);
109
120
 
110
- recursion_guard_ = false;
121
+ global_recursion_guard_ = false;
111
122
  }
112
123
  #endif // USE_STORE_LOG_STR_IN_FLASH
113
124
 
@@ -179,7 +190,17 @@ void Logger::loop() {
179
190
  this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
180
191
  this->tx_buffer_[this->tx_buffer_at_] = '\0';
181
192
  this->call_log_callbacks_(message->level, message->tag, this->tx_buffer_);
193
+ // At this point all the data we need from message has been transferred to the tx_buffer
194
+ // so we can release the message to allow other tasks to use it as soon as possible.
182
195
  this->log_buffer_->release_message_main_loop(received_token);
196
+
197
+ // Write to console from the main loop to prevent corruption from concurrent writes
198
+ // This ensures all log messages appear on the console in a clean, serialized manner
199
+ // Note: Messages may appear slightly out of order due to async processing, but
200
+ // this is preferred over corrupted/interleaved console output
201
+ if (this->baud_rate_ > 0) {
202
+ this->write_msg_(this->tx_buffer_);
203
+ }
183
204
  }
184
205
  }
185
206
  #endif
@@ -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