esphome 2025.7.0b1__py3-none-any.whl → 2025.7.0b2__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 (49) hide show
  1. esphome/components/api/__init__.py +19 -5
  2. esphome/components/api/api_connection.cpp +53 -175
  3. esphome/components/api/api_connection.h +28 -25
  4. esphome/components/api/api_frame_helper.cpp +2 -3
  5. esphome/components/api/api_frame_helper.h +7 -9
  6. esphome/components/api/api_pb2.cpp +1862 -5133
  7. esphome/components/api/api_pb2.h +291 -533
  8. esphome/components/api/api_pb2_dump.cpp +99 -0
  9. esphome/components/api/api_pb2_service.cpp +4 -0
  10. esphome/components/api/api_pb2_service.h +6 -0
  11. esphome/components/api/api_server.cpp +2 -9
  12. esphome/components/api/api_server.h +7 -33
  13. esphome/components/api/custom_api_device.h +8 -0
  14. esphome/components/api/list_entities.cpp +2 -0
  15. esphome/components/api/list_entities.h +3 -1
  16. esphome/components/api/proto.h +506 -23
  17. esphome/components/api/user_services.h +2 -0
  18. esphome/components/debug/debug_esp32.cpp +2 -0
  19. esphome/components/esp32/__init__.py +1 -0
  20. esphome/components/esp32_camera/__init__.py +1 -1
  21. esphome/components/esp32_touch/esp32_touch_v1.cpp +12 -10
  22. esphome/components/esp8266/__init__.py +1 -0
  23. esphome/components/host/__init__.py +1 -0
  24. esphome/components/ld2410/ld2410.cpp +12 -28
  25. esphome/components/libretiny/__init__.py +1 -0
  26. esphome/components/mqtt/mqtt_backend_esp32.cpp +6 -2
  27. esphome/components/packet_transport/packet_transport.cpp +3 -0
  28. esphome/components/rp2040/__init__.py +1 -0
  29. esphome/components/sx126x/__init__.py +3 -3
  30. esphome/components/sx127x/__init__.py +2 -2
  31. esphome/components/usb_host/usb_host_client.cpp +10 -10
  32. esphome/components/usb_uart/cp210x.cpp +1 -1
  33. esphome/components/usb_uart/usb_uart.cpp +41 -44
  34. esphome/components/usb_uart/usb_uart.h +4 -3
  35. esphome/const.py +1 -1
  36. esphome/core/component_iterator.cpp +4 -2
  37. esphome/core/component_iterator.h +3 -3
  38. esphome/core/defines.h +1 -1
  39. esphome/core/entity_helpers.py +6 -0
  40. esphome/core/scheduler.cpp +9 -12
  41. esphome/core/scheduler.h +0 -3
  42. esphome/wizard.py +1 -1
  43. {esphome-2025.7.0b1.dist-info → esphome-2025.7.0b2.dist-info}/METADATA +2 -2
  44. {esphome-2025.7.0b1.dist-info → esphome-2025.7.0b2.dist-info}/RECORD +48 -49
  45. esphome/components/api/api_pb2_size.h +0 -359
  46. {esphome-2025.7.0b1.dist-info → esphome-2025.7.0b2.dist-info}/WHEEL +0 -0
  47. {esphome-2025.7.0b1.dist-info → esphome-2025.7.0b2.dist-info}/entry_points.txt +0 -0
  48. {esphome-2025.7.0b1.dist-info → esphome-2025.7.0b2.dist-info}/licenses/LICENSE +0 -0
  49. {esphome-2025.7.0b1.dist-info → esphome-2025.7.0b2.dist-info}/top_level.txt +0 -0
@@ -45,3 +45,4 @@ async def to_code(config):
45
45
  cg.add_define("ESPHOME_BOARD", "host")
46
46
  cg.add_platformio_option("platform", "platformio/native")
47
47
  cg.add_platformio_option("lib_ldf_mode", "off")
48
+ cg.add_platformio_option("lib_compat_mode", "strict")
@@ -178,13 +178,8 @@ static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
178
178
 
179
179
  static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
180
180
 
181
- static bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
182
- for (uint8_t i = 0; i < HEADER_FOOTER_SIZE; i++) {
183
- if (header_footer[i] != buffer[i]) {
184
- return false; // Mismatch in header/footer
185
- }
186
- }
187
- return true; // Valid header/footer
181
+ static inline bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
182
+ return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0;
188
183
  }
189
184
 
190
185
  void LD2410Component::dump_config() {
@@ -300,14 +295,12 @@ void LD2410Component::send_command_(uint8_t command, const uint8_t *command_valu
300
295
  if (command_value != nullptr) {
301
296
  len += command_value_len;
302
297
  }
303
- uint8_t len_cmd[] = {lowbyte(len), highbyte(len), command, 0x00};
298
+ // 2 length bytes (low, high) + 2 command bytes (low, high)
299
+ uint8_t len_cmd[] = {len, 0x00, command, 0x00};
304
300
  this->write_array(len_cmd, sizeof(len_cmd));
305
-
306
301
  // command value bytes
307
302
  if (command_value != nullptr) {
308
- for (uint8_t i = 0; i < command_value_len; i++) {
309
- this->write_byte(command_value[i]);
310
- }
303
+ this->write_array(command_value, command_value_len);
311
304
  }
312
305
  // frame footer bytes
313
306
  this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER));
@@ -401,7 +394,7 @@ void LD2410Component::handle_periodic_data_() {
401
394
  /*
402
395
  Moving distance range: 18th byte
403
396
  Still distance range: 19th byte
404
- Moving enery: 20~28th bytes
397
+ Moving energy: 20~28th bytes
405
398
  */
406
399
  for (std::vector<sensor::Sensor *>::size_type i = 0; i != this->gate_move_sensors_.size(); i++) {
407
400
  sensor::Sensor *s = this->gate_move_sensors_[i];
@@ -480,7 +473,7 @@ bool LD2410Component::handle_ack_data_() {
480
473
  ESP_LOGE(TAG, "Invalid status");
481
474
  return true;
482
475
  }
483
- if (ld2410::two_byte_to_int(this->buffer_data_[8], this->buffer_data_[9]) != 0x00) {
476
+ if (this->buffer_data_[8] || this->buffer_data_[9]) {
484
477
  ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]);
485
478
  return true;
486
479
  }
@@ -534,8 +527,8 @@ bool LD2410Component::handle_ack_data_() {
534
527
  const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_);
535
528
  const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_);
536
529
  ESP_LOGV(TAG,
537
- "Light function is: %s\n"
538
- "Light threshold is: %u\n"
530
+ "Light function: %s\n"
531
+ "Light threshold: %u\n"
539
532
  "Out pin level: %s",
540
533
  light_function_str, this->light_threshold_, out_pin_level_str);
541
534
  #ifdef USE_SELECT
@@ -600,7 +593,7 @@ bool LD2410Component::handle_ack_data_() {
600
593
  break;
601
594
 
602
595
  case CMD_QUERY: { // Query parameters response
603
- if (this->buffer_data_[10] != 0xAA)
596
+ if (this->buffer_data_[10] != HEADER)
604
597
  return true; // value head=0xAA
605
598
  #ifdef USE_NUMBER
606
599
  /*
@@ -656,17 +649,11 @@ void LD2410Component::readline_(int readch) {
656
649
  if (this->buffer_pos_ < 4) {
657
650
  return; // Not enough data to process yet
658
651
  }
659
- if (this->buffer_data_[this->buffer_pos_ - 4] == DATA_FRAME_FOOTER[0] &&
660
- this->buffer_data_[this->buffer_pos_ - 3] == DATA_FRAME_FOOTER[1] &&
661
- this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[2] &&
662
- this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[3]) {
652
+ if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
663
653
  ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
664
654
  this->handle_periodic_data_();
665
655
  this->buffer_pos_ = 0; // Reset position index for next message
666
- } else if (this->buffer_data_[this->buffer_pos_ - 4] == CMD_FRAME_FOOTER[0] &&
667
- this->buffer_data_[this->buffer_pos_ - 3] == CMD_FRAME_FOOTER[1] &&
668
- this->buffer_data_[this->buffer_pos_ - 2] == CMD_FRAME_FOOTER[2] &&
669
- this->buffer_data_[this->buffer_pos_ - 1] == CMD_FRAME_FOOTER[3]) {
656
+ } else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
670
657
  ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
671
658
  if (this->handle_ack_data_()) {
672
659
  this->buffer_pos_ = 0; // Reset position index for next message
@@ -772,7 +759,6 @@ void LD2410Component::set_max_distances_timeout() {
772
759
  0x00};
773
760
  this->set_config_mode_(true);
774
761
  this->send_command_(CMD_MAXDIST_DURATION, value, sizeof(value));
775
- delay(50); // NOLINT
776
762
  this->query_parameters_();
777
763
  this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
778
764
  this->set_config_mode_(false);
@@ -802,7 +788,6 @@ void LD2410Component::set_gate_threshold(uint8_t gate) {
802
788
  0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00,
803
789
  0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00};
804
790
  this->send_command_(CMD_GATE_SENS, value, sizeof(value));
805
- delay(50); // NOLINT
806
791
  this->query_parameters_();
807
792
  this->set_config_mode_(false);
808
793
  }
@@ -833,7 +818,6 @@ void LD2410Component::set_light_out_control() {
833
818
  this->set_config_mode_(true);
834
819
  uint8_t value[4] = {this->light_function_, this->light_threshold_, this->out_pin_level_, 0x00};
835
820
  this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value));
836
- delay(50); // NOLINT
837
821
  this->query_light_control_();
838
822
  this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
839
823
  this->set_config_mode_(false);
@@ -268,6 +268,7 @@ async def component_to_code(config):
268
268
 
269
269
  # disable library compatibility checks
270
270
  cg.add_platformio_option("lib_ldf_mode", "off")
271
+ cg.add_platformio_option("lib_compat_mode", "soft")
271
272
  # include <Arduino.h> in every file
272
273
  cg.add_platformio_option("build_src_flags", "-include Arduino.h")
273
274
  # dummy version code
@@ -153,11 +153,15 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) {
153
153
  case MQTT_EVENT_DATA: {
154
154
  static std::string topic;
155
155
  if (!event.topic.empty()) {
156
+ // When a single message arrives as multiple chunks, the topic will be empty
157
+ // on any but the first message, leading to event.topic being an empty string.
158
+ // To ensure handlers get the correct topic, cache the last seen topic to
159
+ // simulate always receiving the topic from underlying library
156
160
  topic = event.topic;
157
161
  }
158
162
  ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str());
159
- this->on_message_.call(!event.topic.empty() ? topic.c_str() : nullptr, event.data.data(), event.data.size(),
160
- event.current_data_offset, event.total_data_len);
163
+ this->on_message_.call(topic.c_str(), event.data.data(), event.data.size(), event.current_data_offset,
164
+ event.total_data_len);
161
165
  } break;
162
166
  case MQTT_EVENT_ERROR:
163
167
  ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
@@ -314,6 +314,9 @@ void PacketTransport::send_data_(bool all) {
314
314
  }
315
315
 
316
316
  void PacketTransport::update() {
317
+ if (!this->ping_pong_enable_) {
318
+ return;
319
+ }
317
320
  auto now = millis() / 1000;
318
321
  if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) {
319
322
  this->resend_ping_key_ = this->ping_pong_enable_;
@@ -165,6 +165,7 @@ async def to_code(config):
165
165
  # Allow LDF to properly discover dependency including those in preprocessor
166
166
  # conditionals
167
167
  cg.add_platformio_option("lib_ldf_mode", "chain+")
168
+ cg.add_platformio_option("lib_compat_mode", "strict")
168
169
  cg.add_platformio_option("board", config[CONF_BOARD])
169
170
  cg.add_build_flag("-DUSE_RP2040")
170
171
  cg.set_cpp_standard("gnu++20")
@@ -167,8 +167,8 @@ def validate_config(config):
167
167
  if config[CONF_MODULATION] == "LORA":
168
168
  if config[CONF_BANDWIDTH] not in lora_bws:
169
169
  raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA")
170
- if config[CONF_PREAMBLE_SIZE] > 0 and config[CONF_PREAMBLE_SIZE] < 6:
171
- raise cv.Invalid("Minimum preamble size is 6 with LORA")
170
+ if config[CONF_PREAMBLE_SIZE] < 6:
171
+ raise cv.Invalid("Minimum 'preamble_size' is 6 with LORA")
172
172
  if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0:
173
173
  raise cv.Invalid("Payload length must be set when spreading factor is 6")
174
174
  else:
@@ -200,7 +200,7 @@ CONFIG_SCHEMA = (
200
200
  cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP),
201
201
  cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256),
202
202
  cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4),
203
- cv.Required(CONF_PREAMBLE_SIZE): cv.int_range(min=1, max=65535),
203
+ cv.Optional(CONF_PREAMBLE_SIZE, default=8): cv.int_range(min=1, max=65535),
204
204
  cv.Required(CONF_RST_PIN): pins.internal_gpio_output_pin_schema,
205
205
  cv.Optional(CONF_RX_START, default=True): cv.boolean,
206
206
  cv.Required(CONF_RF_SWITCH): cv.boolean,
@@ -164,8 +164,8 @@ def validate_config(config):
164
164
  raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA")
165
165
  if CONF_DIO0_PIN not in config:
166
166
  raise cv.Invalid("Cannot use LoRa without dio0_pin")
167
- if 0 < config[CONF_PREAMBLE_SIZE] < 6:
168
- raise cv.Invalid("Minimum preamble size is 6 with LORA")
167
+ if config[CONF_PREAMBLE_SIZE] < 6:
168
+ raise cv.Invalid("Minimum 'preamble_size' is 6 with LORA")
169
169
  if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0:
170
170
  raise cv.Invalid("Payload length must be set when spreading factor is 6")
171
171
  else:
@@ -70,7 +70,7 @@ static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) {
70
70
  ESP_LOGV(TAG, "bMaxPower %dmA", cfg_desc->bMaxPower * 2);
71
71
  }
72
72
 
73
- void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
73
+ static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
74
74
  if (devc_desc == NULL) {
75
75
  return;
76
76
  }
@@ -92,8 +92,8 @@ void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
92
92
  ESP_LOGV(TAG, "bNumConfigurations %d", devc_desc->bNumConfigurations);
93
93
  }
94
94
 
95
- void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,
96
- print_class_descriptor_cb class_specific_cb) {
95
+ static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,
96
+ print_class_descriptor_cb class_specific_cb) {
97
97
  if (cfg_desc == nullptr) {
98
98
  return;
99
99
  }
@@ -128,9 +128,9 @@ void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,
128
128
  static std::string get_descriptor_string(const usb_str_desc_t *desc) {
129
129
  char buffer[256];
130
130
  if (desc == nullptr)
131
- return "(unknown)";
131
+ return "(unspecified)";
132
132
  char *p = buffer;
133
- for (size_t i = 0; i != desc->bLength / 2; i++) {
133
+ for (int i = 0; i != desc->bLength / 2; i++) {
134
134
  auto c = desc->wData[i];
135
135
  if (c < 0x100)
136
136
  *p++ = static_cast<char>(c);
@@ -169,7 +169,7 @@ void USBClient::setup() {
169
169
  this->mark_failed();
170
170
  return;
171
171
  }
172
- for (auto trq : this->trq_pool_) {
172
+ for (auto *trq : this->trq_pool_) {
173
173
  usb_host_transfer_alloc(64, 0, &trq->transfer);
174
174
  trq->client = this;
175
175
  }
@@ -197,7 +197,8 @@ void USBClient::loop() {
197
197
  ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct);
198
198
  if (desc->idVendor == this->vid_ && desc->idProduct == this->pid_ || this->vid_ == 0 && this->pid_ == 0) {
199
199
  usb_device_info_t dev_info;
200
- if ((err = usb_host_device_info(this->device_handle_, &dev_info)) != ESP_OK) {
200
+ err = usb_host_device_info(this->device_handle_, &dev_info);
201
+ if (err != ESP_OK) {
201
202
  ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err));
202
203
  this->disconnect();
203
204
  break;
@@ -336,7 +337,7 @@ static void transfer_callback(usb_transfer_t *xfer) {
336
337
  * @throws None.
337
338
  */
338
339
  void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) {
339
- auto trq = this->get_trq_();
340
+ auto *trq = this->get_trq_();
340
341
  if (trq == nullptr) {
341
342
  ESP_LOGE(TAG, "Too many requests queued");
342
343
  return;
@@ -349,7 +350,6 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
349
350
  if (err != ESP_OK) {
350
351
  ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
351
352
  this->release_trq(trq);
352
- this->disconnect();
353
353
  }
354
354
  }
355
355
 
@@ -364,7 +364,7 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
364
364
  * @throws None.
365
365
  */
366
366
  void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) {
367
- auto trq = this->get_trq_();
367
+ auto *trq = this->get_trq_();
368
368
  if (trq == nullptr) {
369
369
  ESP_LOGE(TAG, "Too many requests queued");
370
370
  return;
@@ -43,7 +43,7 @@ static constexpr uint8_t SET_BAUDRATE = 0x1E; // Set the baud rate.
43
43
  static constexpr uint8_t SET_CHARS = 0x19; // Set special characters.
44
44
  static constexpr uint8_t VENDOR_SPECIFIC = 0xFF; // Vendor specific command.
45
45
 
46
- std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors_(usb_device_handle_t dev_hdl) {
46
+ std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev_hdl) {
47
47
  const usb_config_desc_t *config_desc;
48
48
  const usb_device_desc_t *device_desc;
49
49
  int conf_offset = 0, ep_offset;
@@ -18,52 +18,48 @@ namespace usb_uart {
18
18
  */
19
19
  static optional<CdcEps> get_cdc(const usb_config_desc_t *config_desc, uint8_t intf_idx) {
20
20
  int conf_offset, ep_offset;
21
- const usb_ep_desc_t *notify_ep{}, *in_ep{}, *out_ep{};
22
- uint8_t interface_number = 0;
23
- // look for an interface with one interrupt endpoint (notify), and an interface with two bulk endpoints (data in/out)
21
+ // look for an interface with an interrupt endpoint (notify), and one with two bulk endpoints (data in/out)
22
+ CdcEps eps{};
23
+ eps.bulk_interface_number = 0xFF;
24
24
  for (;;) {
25
- auto intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset);
25
+ const auto *intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset);
26
26
  if (!intf_desc) {
27
27
  ESP_LOGE(TAG, "usb_parse_interface_descriptor failed");
28
28
  return nullopt;
29
29
  }
30
- if (intf_desc->bNumEndpoints == 1) {
30
+ ESP_LOGD(TAG, "intf_desc: bInterfaceClass=%02X, bInterfaceSubClass=%02X, bInterfaceProtocol=%02X, bNumEndpoints=%d",
31
+ intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol,
32
+ intf_desc->bNumEndpoints);
33
+ for (uint8_t i = 0; i != intf_desc->bNumEndpoints; i++) {
31
34
  ep_offset = conf_offset;
32
- notify_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset);
33
- if (!notify_ep) {
34
- ESP_LOGE(TAG, "notify_ep: usb_parse_endpoint_descriptor_by_index failed");
35
+ const auto *ep = usb_parse_endpoint_descriptor_by_index(intf_desc, i, config_desc->wTotalLength, &ep_offset);
36
+ if (!ep) {
37
+ ESP_LOGE(TAG, "Ran out of interfaces at %d before finding all endpoints", i);
35
38
  return nullopt;
36
39
  }
37
- if (notify_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_INT)
38
- notify_ep = nullptr;
39
- } else if (USB_CLASS_CDC_DATA && intf_desc->bNumEndpoints == 2) {
40
- interface_number = intf_desc->bInterfaceNumber;
41
- ep_offset = conf_offset;
42
- out_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset);
43
- if (!out_ep) {
44
- ESP_LOGE(TAG, "out_ep: usb_parse_endpoint_descriptor_by_index failed");
45
- return nullopt;
46
- }
47
- if (out_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
48
- out_ep = nullptr;
49
- ep_offset = conf_offset;
50
- in_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 1, config_desc->wTotalLength, &ep_offset);
51
- if (!in_ep) {
52
- ESP_LOGE(TAG, "in_ep: usb_parse_endpoint_descriptor_by_index failed");
53
- return nullopt;
40
+ ESP_LOGD(TAG, "ep: bEndpointAddress=%02X, bmAttributes=%02X", ep->bEndpointAddress, ep->bmAttributes);
41
+ if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_INT) {
42
+ eps.notify_ep = ep;
43
+ eps.interrupt_interface_number = intf_desc->bInterfaceNumber;
44
+ } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && ep->bEndpointAddress & usb_host::USB_DIR_IN &&
45
+ (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
46
+ eps.in_ep = ep;
47
+ eps.bulk_interface_number = intf_desc->bInterfaceNumber;
48
+ } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && !(ep->bEndpointAddress & usb_host::USB_DIR_IN) &&
49
+ (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
50
+ eps.out_ep = ep;
51
+ eps.bulk_interface_number = intf_desc->bInterfaceNumber;
52
+ } else {
53
+ ESP_LOGE(TAG, "Unexpected endpoint attributes: %02X", ep->bmAttributes);
54
+ continue;
54
55
  }
55
- if (in_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
56
- in_ep = nullptr;
57
56
  }
58
- if (in_ep != nullptr && out_ep != nullptr && notify_ep != nullptr)
59
- break;
57
+ if (eps.in_ep != nullptr && eps.out_ep != nullptr && eps.notify_ep != nullptr)
58
+ return eps;
60
59
  }
61
- if (in_ep->bEndpointAddress & usb_host::USB_DIR_IN)
62
- return CdcEps{notify_ep, in_ep, out_ep, interface_number};
63
- return CdcEps{notify_ep, out_ep, in_ep, interface_number};
64
60
  }
65
61
 
66
- std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t dev_hdl) {
62
+ std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors(usb_device_handle_t dev_hdl) {
67
63
  const usb_config_desc_t *config_desc;
68
64
  const usb_device_desc_t *device_desc;
69
65
  int desc_offset = 0;
@@ -78,7 +74,7 @@ std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t de
78
74
  ESP_LOGE(TAG, "get_active_config_descriptor failed");
79
75
  return {};
80
76
  }
81
- if (device_desc->bDeviceClass == USB_CLASS_COMM) {
77
+ if (device_desc->bDeviceClass == USB_CLASS_COMM || device_desc->bDeviceClass == USB_CLASS_VENDOR_SPEC) {
82
78
  // single CDC-ACM device
83
79
  if (auto eps = get_cdc(config_desc, 0)) {
84
80
  ESP_LOGV(TAG, "Found CDC-ACM device");
@@ -194,7 +190,7 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
194
190
  if (!channel->initialised_ || channel->input_started_ ||
195
191
  channel->input_buffer_.get_free_space() < channel->cdc_dev_.in_ep->wMaxPacketSize)
196
192
  return;
197
- auto ep = channel->cdc_dev_.in_ep;
193
+ const auto *ep = channel->cdc_dev_.in_ep;
198
194
  auto callback = [this, channel](const usb_host::TransferStatus &status) {
199
195
  ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
200
196
  if (!status.success) {
@@ -227,7 +223,7 @@ void USBUartComponent::start_output(USBUartChannel *channel) {
227
223
  if (channel->output_buffer_.is_empty()) {
228
224
  return;
229
225
  }
230
- auto ep = channel->cdc_dev_.out_ep;
226
+ const auto *ep = channel->cdc_dev_.out_ep;
231
227
  auto callback = [this, channel](const usb_host::TransferStatus &status) {
232
228
  ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code);
233
229
  channel->output_started_ = false;
@@ -259,15 +255,15 @@ static void fix_mps(const usb_ep_desc_t *ep) {
259
255
  }
260
256
  }
261
257
  void USBUartTypeCdcAcm::on_connected() {
262
- auto cdc_devs = this->parse_descriptors_(this->device_handle_);
258
+ auto cdc_devs = this->parse_descriptors(this->device_handle_);
263
259
  if (cdc_devs.empty()) {
264
260
  this->status_set_error("No CDC-ACM device found");
265
261
  this->disconnect();
266
262
  return;
267
263
  }
268
264
  ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size());
269
- auto i = 0;
270
- for (auto channel : this->channels_) {
265
+ size_t i = 0;
266
+ for (auto *channel : this->channels_) {
271
267
  if (i == cdc_devs.size()) {
272
268
  ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
273
269
  this->status_set_warning("No configuration found for channel");
@@ -277,10 +273,11 @@ void USBUartTypeCdcAcm::on_connected() {
277
273
  fix_mps(channel->cdc_dev_.in_ep);
278
274
  fix_mps(channel->cdc_dev_.out_ep);
279
275
  channel->initialised_ = true;
280
- auto err = usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number, 0);
276
+ auto err =
277
+ usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number, 0);
281
278
  if (err != ESP_OK) {
282
279
  ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
283
- channel->cdc_dev_.interface_number);
280
+ channel->cdc_dev_.bulk_interface_number);
284
281
  this->status_set_error("usb_host_interface_claim failed");
285
282
  this->disconnect();
286
283
  return;
@@ -290,7 +287,7 @@ void USBUartTypeCdcAcm::on_connected() {
290
287
  }
291
288
 
292
289
  void USBUartTypeCdcAcm::on_disconnected() {
293
- for (auto channel : this->channels_) {
290
+ for (auto *channel : this->channels_) {
294
291
  if (channel->cdc_dev_.in_ep != nullptr) {
295
292
  usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
296
293
  usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
@@ -303,7 +300,7 @@ void USBUartTypeCdcAcm::on_disconnected() {
303
300
  usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
304
301
  usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
305
302
  }
306
- usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number);
303
+ usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number);
307
304
  channel->initialised_ = false;
308
305
  channel->input_started_ = false;
309
306
  channel->output_started_ = false;
@@ -314,7 +311,7 @@ void USBUartTypeCdcAcm::on_disconnected() {
314
311
  }
315
312
 
316
313
  void USBUartTypeCdcAcm::enable_channels() {
317
- for (auto channel : this->channels_) {
314
+ for (auto *channel : this->channels_) {
318
315
  if (!channel->initialised_)
319
316
  continue;
320
317
  channel->input_started_ = false;
@@ -25,7 +25,8 @@ struct CdcEps {
25
25
  const usb_ep_desc_t *notify_ep;
26
26
  const usb_ep_desc_t *in_ep;
27
27
  const usb_ep_desc_t *out_ep;
28
- uint8_t interface_number;
28
+ uint8_t bulk_interface_number;
29
+ uint8_t interrupt_interface_number;
29
30
  };
30
31
 
31
32
  enum UARTParityOptions {
@@ -123,7 +124,7 @@ class USBUartTypeCdcAcm : public USBUartComponent {
123
124
  USBUartTypeCdcAcm(uint16_t vid, uint16_t pid) : USBUartComponent(vid, pid) {}
124
125
 
125
126
  protected:
126
- virtual std::vector<CdcEps> parse_descriptors_(usb_device_handle_t dev_hdl);
127
+ virtual std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl);
127
128
  void on_connected() override;
128
129
  virtual void enable_channels();
129
130
  void on_disconnected() override;
@@ -134,7 +135,7 @@ class USBUartTypeCP210X : public USBUartTypeCdcAcm {
134
135
  USBUartTypeCP210X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
135
136
 
136
137
  protected:
137
- std::vector<CdcEps> parse_descriptors_(usb_device_handle_t dev_hdl) override;
138
+ std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
138
139
  void enable_channels() override;
139
140
  };
140
141
  class USBUartTypeCH34X : public USBUartTypeCdcAcm {
esphome/const.py CHANGED
@@ -4,7 +4,7 @@ from enum import Enum
4
4
 
5
5
  from esphome.enum import StrEnum
6
6
 
7
- __version__ = "2025.7.0b1"
7
+ __version__ = "2025.7.0b2"
8
8
 
9
9
  ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
10
10
  VALID_SUBSTITUTIONS_CHARACTERS = (
@@ -4,6 +4,8 @@
4
4
 
5
5
  #ifdef USE_API
6
6
  #include "esphome/components/api/api_server.h"
7
+ #endif
8
+ #ifdef USE_API_SERVICES
7
9
  #include "esphome/components/api/user_services.h"
8
10
  #endif
9
11
 
@@ -148,7 +150,7 @@ void ComponentIterator::advance() {
148
150
  }
149
151
  break;
150
152
  #endif
151
- #ifdef USE_API
153
+ #ifdef USE_API_SERVICES
152
154
  case IteratorState ::SERVICE:
153
155
  if (this->at_ >= api::global_api_server->get_user_services().size()) {
154
156
  advance_platform = true;
@@ -383,7 +385,7 @@ void ComponentIterator::advance() {
383
385
  }
384
386
  bool ComponentIterator::on_end() { return true; }
385
387
  bool ComponentIterator::on_begin() { return true; }
386
- #ifdef USE_API
388
+ #ifdef USE_API_SERVICES
387
389
  bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; }
388
390
  #endif
389
391
  #ifdef USE_CAMERA
@@ -10,7 +10,7 @@
10
10
 
11
11
  namespace esphome {
12
12
 
13
- #ifdef USE_API
13
+ #ifdef USE_API_SERVICES
14
14
  namespace api {
15
15
  class UserServiceDescriptor;
16
16
  } // namespace api
@@ -45,7 +45,7 @@ class ComponentIterator {
45
45
  #ifdef USE_TEXT_SENSOR
46
46
  virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
47
47
  #endif
48
- #ifdef USE_API
48
+ #ifdef USE_API_SERVICES
49
49
  virtual bool on_service(api::UserServiceDescriptor *service);
50
50
  #endif
51
51
  #ifdef USE_CAMERA
@@ -122,7 +122,7 @@ class ComponentIterator {
122
122
  #ifdef USE_TEXT_SENSOR
123
123
  TEXT_SENSOR,
124
124
  #endif
125
- #ifdef USE_API
125
+ #ifdef USE_API_SERVICES
126
126
  SERVICE,
127
127
  #endif
128
128
  #ifdef USE_CAMERA
esphome/core/defines.h CHANGED
@@ -108,7 +108,7 @@
108
108
  #define USE_API_CLIENT_DISCONNECTED_TRIGGER
109
109
  #define USE_API_NOISE
110
110
  #define USE_API_PLAINTEXT
111
- #define USE_API_YAML_SERVICES
111
+ #define USE_API_SERVICES
112
112
  #define USE_MD5
113
113
  #define USE_MQTT
114
114
  #define USE_NETWORK
@@ -187,6 +187,12 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy
187
187
  # No name to validate
188
188
  return config
189
189
 
190
+ # Skip validation for internal entities
191
+ # Internal entities are not exposed to Home Assistant and don't use the hash-based
192
+ # entity state tracking system, so name collisions don't matter for them
193
+ if config.get(CONF_INTERNAL, False):
194
+ return config
195
+
190
196
  # Get the entity name
191
197
  entity_name = config[CONF_NAME]
192
198
 
@@ -66,10 +66,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
66
66
 
67
67
  if (delay == SCHEDULER_DONT_RUN) {
68
68
  // Still need to cancel existing timer if name is not empty
69
- if (this->is_name_valid_(name_cstr)) {
70
- LockGuard guard{this->lock_};
71
- this->cancel_item_locked_(component, name_cstr, type);
72
- }
69
+ LockGuard guard{this->lock_};
70
+ this->cancel_item_locked_(component, name_cstr, type);
73
71
  return;
74
72
  }
75
73
 
@@ -125,10 +123,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
125
123
 
126
124
  LockGuard guard{this->lock_};
127
125
  // If name is provided, do atomic cancel-and-add
128
- if (this->is_name_valid_(name_cstr)) {
129
- // Cancel existing items
130
- this->cancel_item_locked_(component, name_cstr, type);
131
- }
126
+ // Cancel existing items
127
+ this->cancel_item_locked_(component, name_cstr, type);
132
128
  // Add new item directly to to_add_
133
129
  // since we have the lock held
134
130
  this->to_add_.push_back(std::move(item));
@@ -442,10 +438,6 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co
442
438
  // Get the name as const char*
443
439
  const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr);
444
440
 
445
- // Handle null or empty names
446
- if (!this->is_name_valid_(name_cstr))
447
- return false;
448
-
449
441
  // obtain lock because this function iterates and can be called from non-loop task context
450
442
  LockGuard guard{this->lock_};
451
443
  return this->cancel_item_locked_(component, name_cstr, type);
@@ -453,6 +445,11 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co
453
445
 
454
446
  // Helper to cancel items by name - must be called with lock held
455
447
  bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type) {
448
+ // Early return if name is invalid - no items to cancel
449
+ if (name_cstr == nullptr || name_cstr[0] == '\0') {
450
+ return false;
451
+ }
452
+
456
453
  size_t total_cancelled = 0;
457
454
 
458
455
  // Check all containers for matching items
esphome/core/scheduler.h CHANGED
@@ -150,9 +150,6 @@ class Scheduler {
150
150
  return is_static_string ? static_cast<const char *>(name_ptr) : static_cast<const std::string *>(name_ptr)->c_str();
151
151
  }
152
152
 
153
- // Helper to check if a name is valid (not null and not empty)
154
- inline bool is_name_valid_(const char *name) { return name != nullptr && name[0] != '\0'; }
155
-
156
153
  // Common implementation for cancel operations
157
154
  bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type);
158
155
 
esphome/wizard.py CHANGED
@@ -411,7 +411,7 @@ def wizard(path):
411
411
  safe_print("Options:")
412
412
  for board_id, board_data in boards_list:
413
413
  safe_print(f" - {board_id} - {board_data['name']}")
414
- boards.append(board_id)
414
+ boards.append(board_id.lower())
415
415
 
416
416
  while True:
417
417
  board = safe_input(color(AnsiFore.BOLD_WHITE, "(board): "))