esphome 2025.7.0b2__py3-none-any.whl → 2025.7.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 (62) hide show
  1. esphome/components/async_tcp/__init__.py +1 -1
  2. esphome/components/esp_ldo/__init__.py +10 -8
  3. esphome/components/esp_ldo/esp_ldo.h +3 -0
  4. esphome/components/fan/fan.cpp +4 -0
  5. esphome/components/gpio/binary_sensor/__init__.py +24 -3
  6. esphome/components/http_request/update/http_request_update.cpp +7 -7
  7. esphome/components/i2s_audio/speaker/__init__.py +1 -1
  8. esphome/components/json/__init__.py +1 -1
  9. esphome/components/json/json_util.cpp +56 -63
  10. esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp +2 -2
  11. esphome/components/ld2420/button/reconfig_buttons.cpp +1 -1
  12. esphome/components/ld2420/ld2420.cpp +66 -57
  13. esphome/components/ld2420/ld2420.h +9 -11
  14. esphome/components/ld2420/number/gate_config_number.cpp +1 -1
  15. esphome/components/ld2420/select/operating_mode_select.cpp +1 -1
  16. esphome/components/ld2420/sensor/ld2420_sensor.cpp +2 -2
  17. esphome/components/ld2420/text_sensor/text_sensor.cpp +2 -2
  18. esphome/components/light/light_json_schema.cpp +17 -16
  19. esphome/components/lvgl/widgets/meter.py +20 -13
  20. esphome/components/mqtt/mqtt_alarm_control_panel.cpp +2 -1
  21. esphome/components/mqtt/mqtt_binary_sensor.cpp +1 -0
  22. esphome/components/mqtt/mqtt_button.cpp +4 -1
  23. esphome/components/mqtt/mqtt_client.cpp +2 -0
  24. esphome/components/mqtt/mqtt_climate.cpp +6 -4
  25. esphome/components/mqtt/mqtt_component.cpp +3 -1
  26. esphome/components/mqtt/mqtt_cover.cpp +1 -0
  27. esphome/components/mqtt/mqtt_date.cpp +4 -3
  28. esphome/components/mqtt/mqtt_datetime.cpp +7 -6
  29. esphome/components/mqtt/mqtt_event.cpp +6 -3
  30. esphome/components/mqtt/mqtt_fan.cpp +1 -0
  31. esphome/components/mqtt/mqtt_light.cpp +8 -4
  32. esphome/components/mqtt/mqtt_lock.cpp +3 -1
  33. esphome/components/mqtt/mqtt_number.cpp +1 -0
  34. esphome/components/mqtt/mqtt_select.cpp +2 -1
  35. esphome/components/mqtt/mqtt_sensor.cpp +3 -1
  36. esphome/components/mqtt/mqtt_switch.cpp +3 -1
  37. esphome/components/mqtt/mqtt_text.cpp +1 -0
  38. esphome/components/mqtt/mqtt_text_sensor.cpp +3 -1
  39. esphome/components/mqtt/mqtt_time.cpp +4 -3
  40. esphome/components/mqtt/mqtt_update.cpp +1 -0
  41. esphome/components/mqtt/mqtt_valve.cpp +3 -1
  42. esphome/components/ms8607/ms8607.cpp +1 -1
  43. esphome/components/online_image/__init__.py +4 -1
  44. esphome/components/online_image/online_image.cpp +11 -5
  45. esphome/components/online_image/online_image.h +6 -1
  46. esphome/components/opentherm/output/output.cpp +1 -1
  47. esphome/components/servo/servo.cpp +2 -2
  48. esphome/components/substitutions/__init__.py +5 -2
  49. esphome/components/web_server/web_server.cpp +13 -9
  50. esphome/components/web_server_base/__init__.py +1 -1
  51. esphome/components/web_server_idf/web_server_idf.cpp +2 -0
  52. esphome/const.py +1 -1
  53. esphome/core/component.cpp +9 -8
  54. esphome/core/helpers.h +1 -1
  55. esphome/platformio_api.py +2 -0
  56. esphome/writer.py +23 -0
  57. {esphome-2025.7.0b2.dist-info → esphome-2025.7.0b4.dist-info}/METADATA +1 -1
  58. {esphome-2025.7.0b2.dist-info → esphome-2025.7.0b4.dist-info}/RECORD +62 -62
  59. {esphome-2025.7.0b2.dist-info → esphome-2025.7.0b4.dist-info}/WHEEL +0 -0
  60. {esphome-2025.7.0b2.dist-info → esphome-2025.7.0b4.dist-info}/entry_points.txt +0 -0
  61. {esphome-2025.7.0b2.dist-info → esphome-2025.7.0b4.dist-info}/licenses/LICENSE +0 -0
  62. {esphome-2025.7.0b2.dist-info → esphome-2025.7.0b4.dist-info}/top_level.txt +0 -0
@@ -31,7 +31,7 @@ CONFIG_SCHEMA = cv.All(
31
31
  async def to_code(config):
32
32
  if CORE.is_esp32 or CORE.is_libretiny:
33
33
  # https://github.com/ESP32Async/AsyncTCP
34
- cg.add_library("ESP32Async/AsyncTCP", "3.4.4")
34
+ cg.add_library("ESP32Async/AsyncTCP", "3.4.5")
35
35
  elif CORE.is_esp8266:
36
36
  # https://github.com/ESP32Async/ESPAsyncTCP
37
37
  cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")
@@ -20,14 +20,16 @@ adjusted_ids = set()
20
20
 
21
21
  CONFIG_SCHEMA = cv.All(
22
22
  cv.ensure_list(
23
- {
24
- cv.GenerateID(): cv.declare_id(EspLdo),
25
- cv.Required(CONF_VOLTAGE): cv.All(
26
- cv.voltage, cv.float_range(min=0.5, max=2.7)
27
- ),
28
- cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True),
29
- cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean,
30
- }
23
+ cv.COMPONENT_SCHEMA.extend(
24
+ {
25
+ cv.GenerateID(): cv.declare_id(EspLdo),
26
+ cv.Required(CONF_VOLTAGE): cv.All(
27
+ cv.voltage, cv.float_range(min=0.5, max=2.7)
28
+ ),
29
+ cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True),
30
+ cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean,
31
+ }
32
+ )
31
33
  ),
32
34
  cv.only_with_esp_idf,
33
35
  only_on_variant(supported=[VARIANT_ESP32P4]),
@@ -17,6 +17,9 @@ class EspLdo : public Component {
17
17
  void set_adjustable(bool adjustable) { this->adjustable_ = adjustable; }
18
18
  void set_voltage(float voltage) { this->voltage_ = voltage; }
19
19
  void adjust_voltage(float voltage);
20
+ float get_setup_priority() const override {
21
+ return setup_priority::BUS; // LDO setup should be done early
22
+ }
20
23
 
21
24
  protected:
22
25
  int channel_;
@@ -177,6 +177,10 @@ optional<FanRestoreState> Fan::restore_state_() {
177
177
  return {};
178
178
  }
179
179
  void Fan::save_state_() {
180
+ if (this->restore_mode_ == FanRestoreMode::NO_RESTORE) {
181
+ return;
182
+ }
183
+
180
184
  FanRestoreState state{};
181
185
  state.state = this->state;
182
186
  state.oscillating = this->oscillating;
@@ -1,11 +1,16 @@
1
+ import logging
2
+
1
3
  from esphome import pins
2
4
  import esphome.codegen as cg
3
5
  from esphome.components import binary_sensor
4
6
  import esphome.config_validation as cv
5
- from esphome.const import CONF_PIN
7
+ from esphome.const import CONF_ID, CONF_NAME, CONF_NUMBER, CONF_PIN
8
+ from esphome.core import CORE
6
9
 
7
10
  from .. import gpio_ns
8
11
 
12
+ _LOGGER = logging.getLogger(__name__)
13
+
9
14
  GPIOBinarySensor = gpio_ns.class_(
10
15
  "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component
11
16
  )
@@ -41,6 +46,22 @@ async def to_code(config):
41
46
  pin = await cg.gpio_pin_expression(config[CONF_PIN])
42
47
  cg.add(var.set_pin(pin))
43
48
 
44
- cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT]))
45
- if config[CONF_USE_INTERRUPT]:
49
+ # Check for ESP8266 GPIO16 interrupt limitation
50
+ # GPIO16 on ESP8266 is a special pin that doesn't support interrupts through
51
+ # the Arduino attachInterrupt() function. This is the only known GPIO pin
52
+ # across all supported platforms that has this limitation, so we handle it
53
+ # here instead of in the platform-specific code.
54
+ use_interrupt = config[CONF_USE_INTERRUPT]
55
+ if use_interrupt and CORE.is_esp8266 and config[CONF_PIN][CONF_NUMBER] == 16:
56
+ _LOGGER.warning(
57
+ "GPIO binary_sensor '%s': GPIO16 on ESP8266 doesn't support interrupts. "
58
+ "Falling back to polling mode (same as in ESPHome <2025.7). "
59
+ "The sensor will work exactly as before, but other pins have better "
60
+ "performance with interrupts.",
61
+ config.get(CONF_NAME, config[CONF_ID]),
62
+ )
63
+ use_interrupt = False
64
+
65
+ cg.add(var.set_use_interrupt(use_interrupt))
66
+ if use_interrupt:
46
67
  cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE]))
@@ -83,7 +83,7 @@ void HttpRequestUpdate::update_task(void *params) {
83
83
  container.reset(); // Release ownership of the container's shared_ptr
84
84
 
85
85
  valid = json::parse_json(response, [this_update](JsonObject root) -> bool {
86
- if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
86
+ if (!root["name"].is<const char *>() || !root["version"].is<const char *>() || !root["builds"].is<JsonArray>()) {
87
87
  ESP_LOGE(TAG, "Manifest does not contain required fields");
88
88
  return false;
89
89
  }
@@ -91,26 +91,26 @@ void HttpRequestUpdate::update_task(void *params) {
91
91
  this_update->update_info_.latest_version = root["version"].as<std::string>();
92
92
 
93
93
  for (auto build : root["builds"].as<JsonArray>()) {
94
- if (!build.containsKey("chipFamily")) {
94
+ if (!build["chipFamily"].is<const char *>()) {
95
95
  ESP_LOGE(TAG, "Manifest does not contain required fields");
96
96
  return false;
97
97
  }
98
98
  if (build["chipFamily"] == ESPHOME_VARIANT) {
99
- if (!build.containsKey("ota")) {
99
+ if (!build["ota"].is<JsonObject>()) {
100
100
  ESP_LOGE(TAG, "Manifest does not contain required fields");
101
101
  return false;
102
102
  }
103
- auto ota = build["ota"];
104
- if (!ota.containsKey("path") || !ota.containsKey("md5")) {
103
+ JsonObject ota = build["ota"].as<JsonObject>();
104
+ if (!ota["path"].is<const char *>() || !ota["md5"].is<const char *>()) {
105
105
  ESP_LOGE(TAG, "Manifest does not contain required fields");
106
106
  return false;
107
107
  }
108
108
  this_update->update_info_.firmware_url = ota["path"].as<std::string>();
109
109
  this_update->update_info_.md5 = ota["md5"].as<std::string>();
110
110
 
111
- if (ota.containsKey("summary"))
111
+ if (ota["summary"].is<const char *>())
112
112
  this_update->update_info_.summary = ota["summary"].as<std::string>();
113
- if (ota.containsKey("release_url"))
113
+ if (ota["release_url"].is<const char *>())
114
114
  this_update->update_info_.release_url = ota["release_url"].as<std::string>();
115
115
 
116
116
  return true;
@@ -180,7 +180,7 @@ async def to_code(config):
180
180
  await speaker.register_speaker(var, config)
181
181
 
182
182
  if config[CONF_DAC_TYPE] == "internal":
183
- cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
183
+ cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
184
184
  else:
185
185
  cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
186
186
  if use_legacy():
@@ -12,6 +12,6 @@ CONFIG_SCHEMA = cv.All(
12
12
 
13
13
  @coroutine_with_priority(1.0)
14
14
  async def to_code(config):
15
- cg.add_library("bblanchon/ArduinoJson", "6.18.5")
15
+ cg.add_library("bblanchon/ArduinoJson", "7.4.2")
16
16
  cg.add_define("USE_JSON")
17
17
  cg.add_global(json_ns.using)
@@ -1,83 +1,76 @@
1
1
  #include "json_util.h"
2
2
  #include "esphome/core/log.h"
3
3
 
4
+ // ArduinoJson::Allocator is included via ArduinoJson.h in json_util.h
5
+
4
6
  namespace esphome {
5
7
  namespace json {
6
8
 
7
9
  static const char *const TAG = "json";
8
10
 
9
- static std::vector<char> global_json_build_buffer; // NOLINT
10
- static const auto ALLOCATOR = RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::ALLOC_INTERNAL);
11
+ // Build an allocator for the JSON Library using the RAMAllocator class
12
+ struct SpiRamAllocator : ArduinoJson::Allocator {
13
+ void *allocate(size_t size) override { return this->allocator_.allocate(size); }
14
+
15
+ void deallocate(void *pointer) override {
16
+ // ArduinoJson's Allocator interface doesn't provide the size parameter in deallocate.
17
+ // RAMAllocator::deallocate() requires the size, which we don't have access to here.
18
+ // RAMAllocator::deallocate implementation just calls free() regardless of whether
19
+ // the memory was allocated with heap_caps_malloc or malloc.
20
+ // This is safe because ESP-IDF's heap implementation internally tracks the memory region
21
+ // and routes free() to the appropriate heap.
22
+ free(pointer); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
23
+ }
24
+
25
+ void *reallocate(void *ptr, size_t new_size) override {
26
+ return this->allocator_.reallocate(static_cast<uint8_t *>(ptr), new_size);
27
+ }
28
+
29
+ protected:
30
+ RAMAllocator<uint8_t> allocator_{RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::NONE)};
31
+ };
11
32
 
12
33
  std::string build_json(const json_build_t &f) {
13
- // Here we are allocating up to 5kb of memory,
14
- // with the heap size minus 2kb to be safe if less than 5kb
15
- // as we can not have a true dynamic sized document.
16
- // The excess memory is freed below with `shrinkToFit()`
17
- auto free_heap = ALLOCATOR.get_max_free_block_size();
18
- size_t request_size = std::min(free_heap, (size_t) 512);
19
- while (true) {
20
- ESP_LOGV(TAG, "Attempting to allocate %zu bytes for JSON serialization", request_size);
21
- DynamicJsonDocument json_document(request_size);
22
- if (json_document.capacity() == 0) {
23
- ESP_LOGE(TAG, "Could not allocate memory for document! Requested %zu bytes, largest free heap block: %zu bytes",
24
- request_size, free_heap);
25
- return "{}";
26
- }
27
- JsonObject root = json_document.to<JsonObject>();
28
- f(root);
29
- if (json_document.overflowed()) {
30
- if (request_size == free_heap) {
31
- ESP_LOGE(TAG, "Could not allocate memory for document! Overflowed largest free heap block: %zu bytes",
32
- free_heap);
33
- return "{}";
34
- }
35
- request_size = std::min(request_size * 2, free_heap);
36
- continue;
37
- }
38
- json_document.shrinkToFit();
39
- ESP_LOGV(TAG, "Size after shrink %zu bytes", json_document.capacity());
40
- std::string output;
41
- serializeJson(json_document, output);
42
- return output;
34
+ // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
35
+ auto doc_allocator = SpiRamAllocator();
36
+ JsonDocument json_document(&doc_allocator);
37
+ if (json_document.overflowed()) {
38
+ ESP_LOGE(TAG, "Could not allocate memory for JSON document!");
39
+ return "{}";
40
+ }
41
+ JsonObject root = json_document.to<JsonObject>();
42
+ f(root);
43
+ if (json_document.overflowed()) {
44
+ ESP_LOGE(TAG, "Could not allocate memory for JSON document!");
45
+ return "{}";
43
46
  }
47
+ std::string output;
48
+ serializeJson(json_document, output);
49
+ return output;
50
+ // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
44
51
  }
45
52
 
46
53
  bool parse_json(const std::string &data, const json_parse_t &f) {
47
- // Here we are allocating 1.5 times the data size,
48
- // with the heap size minus 2kb to be safe if less than that
49
- // as we can not have a true dynamic sized document.
50
- // The excess memory is freed below with `shrinkToFit()`
51
- auto free_heap = ALLOCATOR.get_max_free_block_size();
52
- size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
53
- while (true) {
54
- DynamicJsonDocument json_document(request_size);
55
- if (json_document.capacity() == 0) {
56
- ESP_LOGE(TAG, "Could not allocate memory for document! Requested %zu bytes, free heap: %zu", request_size,
57
- free_heap);
58
- return false;
59
- }
60
- DeserializationError err = deserializeJson(json_document, data);
61
- json_document.shrinkToFit();
54
+ // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
55
+ auto doc_allocator = SpiRamAllocator();
56
+ JsonDocument json_document(&doc_allocator);
57
+ if (json_document.overflowed()) {
58
+ ESP_LOGE(TAG, "Could not allocate memory for JSON document!");
59
+ return false;
60
+ }
61
+ DeserializationError err = deserializeJson(json_document, data);
62
62
 
63
- JsonObject root = json_document.as<JsonObject>();
63
+ JsonObject root = json_document.as<JsonObject>();
64
64
 
65
- if (err == DeserializationError::Ok) {
66
- return f(root);
67
- } else if (err == DeserializationError::NoMemory) {
68
- if (request_size * 2 >= free_heap) {
69
- ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
70
- return false;
71
- }
72
- ESP_LOGV(TAG, "Increasing memory allocation.");
73
- request_size *= 2;
74
- continue;
75
- } else {
76
- ESP_LOGE(TAG, "Parse error: %s", err.c_str());
77
- return false;
78
- }
79
- };
65
+ if (err == DeserializationError::Ok) {
66
+ return f(root);
67
+ } else if (err == DeserializationError::NoMemory) {
68
+ ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
69
+ return false;
70
+ }
71
+ ESP_LOGE(TAG, "Parse error: %s", err.c_str());
80
72
  return false;
73
+ // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
81
74
  }
82
75
 
83
76
  } // namespace json
@@ -5,10 +5,10 @@
5
5
  namespace esphome {
6
6
  namespace ld2420 {
7
7
 
8
- static const char *const TAG = "LD2420.binary_sensor";
8
+ static const char *const TAG = "ld2420.binary_sensor";
9
9
 
10
10
  void LD2420BinarySensor::dump_config() {
11
- ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:");
11
+ ESP_LOGCONFIG(TAG, "Binary Sensor:");
12
12
  LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_);
13
13
  }
14
14
 
@@ -2,7 +2,7 @@
2
2
  #include "esphome/core/helpers.h"
3
3
  #include "esphome/core/log.h"
4
4
 
5
- static const char *const TAG = "LD2420.button";
5
+ static const char *const TAG = "ld2420.button";
6
6
 
7
7
  namespace esphome {
8
8
  namespace ld2420 {
@@ -137,7 +137,7 @@ static const std::string OP_SIMPLE_MODE_STRING = "Simple";
137
137
  // Memory-efficient lookup tables
138
138
  struct StringToUint8 {
139
139
  const char *str;
140
- uint8_t value;
140
+ const uint8_t value;
141
141
  };
142
142
 
143
143
  static constexpr StringToUint8 OP_MODE_BY_STR[] = {
@@ -155,8 +155,9 @@ static constexpr const char *ERR_MESSAGE[] = {
155
155
  // Helper function for lookups
156
156
  template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
157
157
  for (const auto &entry : arr) {
158
- if (str == entry.str)
158
+ if (str == entry.str) {
159
159
  return entry.value;
160
+ }
160
161
  }
161
162
  return 0xFF; // Not found
162
163
  }
@@ -326,15 +327,8 @@ void LD2420Component::revert_config_action() {
326
327
 
327
328
  void LD2420Component::loop() {
328
329
  // If there is a active send command do not process it here, the send command call will handle it.
329
- if (!this->get_cmd_active_()) {
330
- if (!this->available())
331
- return;
332
- static uint8_t buffer[2048];
333
- static uint8_t rx_data;
334
- while (this->available()) {
335
- rx_data = this->read();
336
- this->readline_(rx_data, buffer, sizeof(buffer));
337
- }
330
+ while (!this->cmd_active_ && this->available()) {
331
+ this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH);
338
332
  }
339
333
  }
340
334
 
@@ -365,8 +359,9 @@ void LD2420Component::auto_calibrate_sensitivity() {
365
359
 
366
360
  // Store average and peak values
367
361
  this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
368
- if (this->gate_peak[gate] < peak)
362
+ if (this->gate_peak[gate] < peak) {
369
363
  this->gate_peak[gate] = peak;
364
+ }
370
365
 
371
366
  uint32_t calculated_value =
372
367
  (static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
@@ -403,8 +398,9 @@ void LD2420Component::set_operating_mode(const std::string &state) {
403
398
  }
404
399
  } else {
405
400
  // Set the current data back so we don't have new data that can be applied in error.
406
- if (this->get_calibration_())
401
+ if (this->get_calibration_()) {
407
402
  memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
403
+ }
408
404
  this->set_calibration_(false);
409
405
  }
410
406
  } else {
@@ -414,30 +410,32 @@ void LD2420Component::set_operating_mode(const std::string &state) {
414
410
  }
415
411
 
416
412
  void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
417
- static int pos = 0;
418
-
419
- if (rx_data >= 0) {
420
- if (pos < len - 1) {
421
- buffer[pos++] = rx_data;
422
- buffer[pos] = 0;
423
- } else {
424
- pos = 0;
425
- }
426
- if (pos >= 4) {
427
- if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
428
- this->set_cmd_active_(false); // Set command state to inactive after responce.
429
- this->handle_ack_data_(buffer, pos);
430
- pos = 0;
431
- } else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) &&
432
- (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
433
- this->handle_simple_mode_(buffer, pos);
434
- pos = 0;
435
- } else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
436
- (this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
437
- this->handle_energy_mode_(buffer, pos);
438
- pos = 0;
439
- }
440
- }
413
+ if (rx_data < 0) {
414
+ return; // No data available
415
+ }
416
+ if (this->buffer_pos_ < len - 1) {
417
+ buffer[this->buffer_pos_++] = rx_data;
418
+ buffer[this->buffer_pos_] = 0;
419
+ } else {
420
+ // We should never get here, but just in case...
421
+ ESP_LOGW(TAG, "Max command length exceeded; ignoring");
422
+ this->buffer_pos_ = 0;
423
+ }
424
+ if (this->buffer_pos_ < 4) {
425
+ return; // Not enough data to process yet
426
+ }
427
+ if (memcmp(&buffer[this->buffer_pos_ - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
428
+ this->cmd_active_ = false; // Set command state to inactive after response
429
+ this->handle_ack_data_(buffer, this->buffer_pos_);
430
+ this->buffer_pos_ = 0;
431
+ } else if ((buffer[this->buffer_pos_ - 2] == 0x0D && buffer[this->buffer_pos_ - 1] == 0x0A) &&
432
+ (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
433
+ this->handle_simple_mode_(buffer, this->buffer_pos_);
434
+ this->buffer_pos_ = 0;
435
+ } else if ((memcmp(&buffer[this->buffer_pos_ - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
436
+ (this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
437
+ this->handle_energy_mode_(buffer, this->buffer_pos_);
438
+ this->buffer_pos_ = 0;
441
439
  }
442
440
  }
443
441
 
@@ -462,8 +460,9 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
462
460
 
463
461
  // Resonable refresh rate for home assistant database size health
464
462
  const int32_t current_millis = App.get_loop_component_start_time();
465
- if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
463
+ if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) {
466
464
  return;
465
+ }
467
466
  this->last_periodic_millis = current_millis;
468
467
  for (auto &listener : this->listeners_) {
469
468
  listener->on_distance(this->get_distance_());
@@ -506,14 +505,16 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
506
505
  }
507
506
  }
508
507
  outbuf[index] = '\0';
509
- if (index > 1)
508
+ if (index > 1) {
510
509
  this->set_distance_(strtol(outbuf, &endptr, 10));
510
+ }
511
511
 
512
512
  if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
513
513
  // Resonable refresh rate for home assistant database size health
514
514
  const int32_t current_millis = App.get_loop_component_start_time();
515
- if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
515
+ if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) {
516
516
  return;
517
+ }
517
518
  this->last_normal_periodic_millis = current_millis;
518
519
  for (auto &listener : this->listeners_)
519
520
  listener->on_distance(this->get_distance_());
@@ -593,11 +594,12 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
593
594
  int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
594
595
  uint32_t start_millis = millis();
595
596
  uint8_t error = 0;
596
- uint8_t ack_buffer[64];
597
- uint8_t cmd_buffer[64];
597
+ uint8_t ack_buffer[MAX_LINE_LENGTH];
598
+ uint8_t cmd_buffer[MAX_LINE_LENGTH];
598
599
  this->cmd_reply_.ack = false;
599
- if (frame.command != CMD_RESTART)
600
- this->set_cmd_active_(true); // Restart does not reply, thus no ack state required.
600
+ if (frame.command != CMD_RESTART) {
601
+ this->cmd_active_ = true;
602
+ } // Restart does not reply, thus no ack state required
601
603
  uint8_t retry = 3;
602
604
  while (retry) {
603
605
  frame.length = 0;
@@ -619,9 +621,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
619
621
 
620
622
  memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
621
623
  frame.length += sizeof(frame.footer);
622
- for (uint16_t index = 0; index < frame.length; index++) {
623
- this->write_byte(cmd_buffer[index]);
624
- }
624
+ this->write_array(cmd_buffer, frame.length);
625
625
 
626
626
  error = 0;
627
627
  if (frame.command == CMD_RESTART) {
@@ -630,7 +630,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
630
630
 
631
631
  while (!this->cmd_reply_.ack) {
632
632
  while (this->available()) {
633
- this->readline_(read(), ack_buffer, sizeof(ack_buffer));
633
+ this->readline_(this->read(), ack_buffer, sizeof(ack_buffer));
634
634
  }
635
635
  delay_microseconds_safe(1450);
636
636
  // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT.
@@ -641,10 +641,12 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
641
641
  break;
642
642
  }
643
643
  }
644
- if (this->cmd_reply_.ack)
644
+ if (this->cmd_reply_.ack) {
645
645
  retry = 0;
646
- if (this->cmd_reply_.error > 0)
646
+ }
647
+ if (this->cmd_reply_.error > 0) {
647
648
  this->handle_cmd_error(error);
649
+ }
648
650
  }
649
651
  return error;
650
652
  }
@@ -764,8 +766,9 @@ void LD2420Component::set_system_mode(uint16_t mode) {
764
766
  cmd_frame.data_length += sizeof(unknown_parm);
765
767
  cmd_frame.footer = CMD_FRAME_FOOTER;
766
768
  ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
767
- if (this->send_cmd_from_array(cmd_frame) == 0)
769
+ if (this->send_cmd_from_array(cmd_frame) == 0) {
768
770
  this->set_mode_(mode);
771
+ }
769
772
  }
770
773
 
771
774
  void LD2420Component::get_firmware_version_() {
@@ -840,18 +843,24 @@ void LD2420Component::set_gate_threshold(uint8_t gate) {
840
843
 
841
844
  #ifdef USE_NUMBER
842
845
  void LD2420Component::init_gate_config_numbers() {
843
- if (this->gate_timeout_number_ != nullptr)
846
+ if (this->gate_timeout_number_ != nullptr) {
844
847
  this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
845
- if (this->gate_select_number_ != nullptr)
848
+ }
849
+ if (this->gate_select_number_ != nullptr) {
846
850
  this->gate_select_number_->publish_state(0);
847
- if (this->min_gate_distance_number_ != nullptr)
851
+ }
852
+ if (this->min_gate_distance_number_ != nullptr) {
848
853
  this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
849
- if (this->max_gate_distance_number_ != nullptr)
854
+ }
855
+ if (this->max_gate_distance_number_ != nullptr) {
850
856
  this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
851
- if (this->gate_move_sensitivity_factor_number_ != nullptr)
857
+ }
858
+ if (this->gate_move_sensitivity_factor_number_ != nullptr) {
852
859
  this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor);
853
- if (this->gate_still_sensitivity_factor_number_ != nullptr)
860
+ }
861
+ if (this->gate_still_sensitivity_factor_number_ != nullptr) {
854
862
  this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor);
863
+ }
855
864
  for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
856
865
  if (this->gate_still_threshold_numbers_[gate] != nullptr) {
857
866
  this->gate_still_threshold_numbers_[gate]->publish_state(
@@ -20,8 +20,9 @@
20
20
  namespace esphome {
21
21
  namespace ld2420 {
22
22
 
23
- static const uint8_t TOTAL_GATES = 16;
24
23
  static const uint8_t CALIBRATE_SAMPLES = 64;
24
+ static const uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer
25
+ static const uint8_t TOTAL_GATES = 16;
25
26
 
26
27
  enum OpMode : uint8_t {
27
28
  OP_NORMAL_MODE = 1,
@@ -118,10 +119,10 @@ class LD2420Component : public Component, public uart::UARTDevice {
118
119
 
119
120
  float gate_move_sensitivity_factor{0.5};
120
121
  float gate_still_sensitivity_factor{0.5};
121
- int32_t last_periodic_millis = millis();
122
- int32_t report_periodic_millis = millis();
123
- int32_t monitor_periodic_millis = millis();
124
- int32_t last_normal_periodic_millis = millis();
122
+ int32_t last_periodic_millis{0};
123
+ int32_t report_periodic_millis{0};
124
+ int32_t monitor_periodic_millis{0};
125
+ int32_t last_normal_periodic_millis{0};
125
126
  uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES];
126
127
  uint16_t gate_avg[TOTAL_GATES];
127
128
  uint16_t gate_peak[TOTAL_GATES];
@@ -161,8 +162,6 @@ class LD2420Component : public Component, public uart::UARTDevice {
161
162
  void set_presence_(bool presence) { this->presence_ = presence; };
162
163
  uint16_t get_distance_() { return this->distance_; };
163
164
  void set_distance_(uint16_t distance) { this->distance_ = distance; };
164
- bool get_cmd_active_() { return this->cmd_active_; };
165
- void set_cmd_active_(bool active) { this->cmd_active_ = active; };
166
165
  void handle_simple_mode_(const uint8_t *inbuf, int len);
167
166
  void handle_energy_mode_(uint8_t *buffer, int len);
168
167
  void handle_ack_data_(uint8_t *buffer, int len);
@@ -181,12 +180,11 @@ class LD2420Component : public Component, public uart::UARTDevice {
181
180
  std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16);
182
181
  #endif
183
182
 
184
- uint32_t max_distance_gate_;
185
- uint32_t min_distance_gate_;
183
+ uint16_t distance_{0};
186
184
  uint16_t system_mode_;
187
185
  uint16_t gate_energy_[TOTAL_GATES];
188
- uint16_t distance_{0};
189
- uint8_t config_checksum_{0};
186
+ uint8_t buffer_pos_{0}; // where to resume processing/populating buffer
187
+ uint8_t buffer_data_[MAX_LINE_LENGTH];
190
188
  char firmware_ver_[8]{"v0.0.0"};
191
189
  bool cmd_active_{false};
192
190
  bool presence_{false};
@@ -2,7 +2,7 @@
2
2
  #include "esphome/core/helpers.h"
3
3
  #include "esphome/core/log.h"
4
4
 
5
- static const char *const TAG = "LD2420.number";
5
+ static const char *const TAG = "ld2420.number";
6
6
 
7
7
  namespace esphome {
8
8
  namespace ld2420 {
@@ -5,7 +5,7 @@
5
5
  namespace esphome {
6
6
  namespace ld2420 {
7
7
 
8
- static const char *const TAG = "LD2420.select";
8
+ static const char *const TAG = "ld2420.select";
9
9
 
10
10
  void LD2420Select::control(const std::string &value) {
11
11
  this->publish_state(value);
@@ -5,10 +5,10 @@
5
5
  namespace esphome {
6
6
  namespace ld2420 {
7
7
 
8
- static const char *const TAG = "LD2420.sensor";
8
+ static const char *const TAG = "ld2420.sensor";
9
9
 
10
10
  void LD2420Sensor::dump_config() {
11
- ESP_LOGCONFIG(TAG, "LD2420 Sensor:");
11
+ ESP_LOGCONFIG(TAG, "Sensor:");
12
12
  LOG_SENSOR(" ", "Distance", this->distance_sensor_);
13
13
  }
14
14