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
@@ -1,6 +1,7 @@
1
1
  #include "feedback_cover.h"
2
2
  #include "esphome/core/hal.h"
3
3
  #include "esphome/core/log.h"
4
+ #include "esphome/core/application.h"
4
5
 
5
6
  namespace esphome {
6
7
  namespace feedback {
@@ -220,7 +221,7 @@ void FeedbackCover::set_open_obstacle_sensor(binary_sensor::BinarySensor *open_o
220
221
  void FeedbackCover::loop() {
221
222
  if (this->current_operation == COVER_OPERATION_IDLE)
222
223
  return;
223
- const uint32_t now = millis();
224
+ const uint32_t now = App.get_loop_component_start_time();
224
225
 
225
226
  // Recompute position every loop cycle
226
227
  this->recompute_position_();
@@ -8,7 +8,7 @@ FujitsuGeneralClimate = fujitsu_general_ns.class_(
8
8
  "FujitsuGeneralClimate", climate_ir.ClimateIR
9
9
  )
10
10
 
11
- CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(FujitsuGeneralClimate)
11
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(FujitsuGeneralClimate)
12
12
 
13
13
 
14
14
  async def to_code(config):
@@ -6,6 +6,7 @@
6
6
  */
7
7
  #include "gcja5.h"
8
8
  #include "esphome/core/log.h"
9
+ #include "esphome/core/application.h"
9
10
  #include <cstring>
10
11
 
11
12
  namespace esphome {
@@ -16,7 +17,7 @@ static const char *const TAG = "gcja5";
16
17
  void GCJA5Component::setup() { ESP_LOGCONFIG(TAG, "Setting up gcja5..."); }
17
18
 
18
19
  void GCJA5Component::loop() {
19
- const uint32_t now = millis();
20
+ const uint32_t now = App.get_loop_component_start_time();
20
21
  if (now - this->last_transmission_ >= 500) {
21
22
  // last transmission too long ago. Reset RX index.
22
23
  this->rx_message_.clear();
@@ -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