mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			6 Commits
		
	
	
		
			jesserockz
			...
			2025.8.0b1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					1c2e1ab3e5 | ||
| 
						 | 
					68ddd98f5f | ||
| 
						 | 
					6b7ced1970 | ||
| 
						 | 
					ed2b76050b | ||
| 
						 | 
					113813617d | ||
| 
						 | 
					c3a209d3f4 | 
							
								
								
									
										2
									
								
								Doxyfile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Doxyfile
									
									
									
									
									
								
							@@ -48,7 +48,7 @@ PROJECT_NAME           = ESPHome
 | 
			
		||||
# could be handy for archiving the generated documentation or if some version
 | 
			
		||||
# control system is used.
 | 
			
		||||
 | 
			
		||||
PROJECT_NUMBER         = 2025.8.0-dev
 | 
			
		||||
PROJECT_NUMBER         = 2025.8.0b1
 | 
			
		||||
 | 
			
		||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
 | 
			
		||||
# for a project that appears at the top of each page and should give viewer a
 | 
			
		||||
 
 | 
			
		||||
@@ -286,6 +286,7 @@ async def remove_bond_to_code(config, action_id, template_arg, args):
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    # Register the loggers this component needs
 | 
			
		||||
    esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
 | 
			
		||||
    cg.add_define("USE_ESP32_BLE_UUID")
 | 
			
		||||
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,16 +12,30 @@ namespace esphome::bluetooth_proxy {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "bluetooth_proxy.connection";
 | 
			
		||||
 | 
			
		||||
// This function is allocation-free and directly packs UUIDs into the output array
 | 
			
		||||
// using precalculated constants for the Bluetooth base UUID
 | 
			
		||||
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
 | 
			
		||||
  esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
 | 
			
		||||
  out[0] = ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
 | 
			
		||||
           ((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
 | 
			
		||||
           ((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
 | 
			
		||||
           ((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
 | 
			
		||||
  out[1] = ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
 | 
			
		||||
           ((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
 | 
			
		||||
           ((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
 | 
			
		||||
           ((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
 | 
			
		||||
  // Bluetooth base UUID: 00000000-0000-1000-8000-00805F9B34FB
 | 
			
		||||
  // out[0] = bytes 8-15 (big-endian)
 | 
			
		||||
  // - For 128-bit UUIDs: use bytes 8-15 as-is
 | 
			
		||||
  // - For 16/32-bit UUIDs: insert into bytes 12-15, use 0x00001000 for bytes 8-11
 | 
			
		||||
  out[0] = uuid_source.len == ESP_UUID_LEN_128
 | 
			
		||||
               ? (((uint64_t) uuid_source.uuid.uuid128[15] << 56) | ((uint64_t) uuid_source.uuid.uuid128[14] << 48) |
 | 
			
		||||
                  ((uint64_t) uuid_source.uuid.uuid128[13] << 40) | ((uint64_t) uuid_source.uuid.uuid128[12] << 32) |
 | 
			
		||||
                  ((uint64_t) uuid_source.uuid.uuid128[11] << 24) | ((uint64_t) uuid_source.uuid.uuid128[10] << 16) |
 | 
			
		||||
                  ((uint64_t) uuid_source.uuid.uuid128[9] << 8) | ((uint64_t) uuid_source.uuid.uuid128[8]))
 | 
			
		||||
               : (((uint64_t) (uuid_source.len == ESP_UUID_LEN_16 ? uuid_source.uuid.uuid16 : uuid_source.uuid.uuid32)
 | 
			
		||||
                   << 32) |
 | 
			
		||||
                  0x00001000ULL);  // Base UUID bytes 8-11
 | 
			
		||||
  // out[1] = bytes 0-7 (big-endian)
 | 
			
		||||
  // - For 128-bit UUIDs: use bytes 0-7 as-is
 | 
			
		||||
  // - For 16/32-bit UUIDs: use precalculated base UUID constant
 | 
			
		||||
  out[1] = uuid_source.len == ESP_UUID_LEN_128
 | 
			
		||||
               ? ((uint64_t) uuid_source.uuid.uuid128[7] << 56) | ((uint64_t) uuid_source.uuid.uuid128[6] << 48) |
 | 
			
		||||
                     ((uint64_t) uuid_source.uuid.uuid128[5] << 40) | ((uint64_t) uuid_source.uuid.uuid128[4] << 32) |
 | 
			
		||||
                     ((uint64_t) uuid_source.uuid.uuid128[3] << 24) | ((uint64_t) uuid_source.uuid.uuid128[2] << 16) |
 | 
			
		||||
                     ((uint64_t) uuid_source.uuid.uuid128[1] << 8) | ((uint64_t) uuid_source.uuid.uuid128[0])
 | 
			
		||||
               : 0x800000805F9B34FBULL;  // Base UUID bytes 0-7: 80-00-00-80-5F-9B-34-FB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Helper to fill UUID in the appropriate format based on client support and UUID type
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
#include <esphome/components/sensor/sensor.h>
 | 
			
		||||
#include <esphome/core/component.h>
 | 
			
		||||
 | 
			
		||||
#define BME280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bme280_base {
 | 
			
		||||
 | 
			
		||||
@@ -98,18 +100,18 @@ void BME280Component::setup() {
 | 
			
		||||
 | 
			
		||||
  if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
 | 
			
		||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (chip_id != 0x60) {
 | 
			
		||||
    this->error_code_ = WRONG_CHIP_ID;
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed(BME280_ERROR_WRONG_CHIP_ID);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Send a soft reset.
 | 
			
		||||
  if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed("Reset failed");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // Wait until the NVM data has finished loading.
 | 
			
		||||
@@ -118,14 +120,12 @@ void BME280Component::setup() {
 | 
			
		||||
  do {  // NOLINT
 | 
			
		||||
    delay(2);
 | 
			
		||||
    if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
 | 
			
		||||
      ESP_LOGW(TAG, "Error reading status register.");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      this->mark_failed("Error reading status register");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  } while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
 | 
			
		||||
  if (status & BME280_STATUS_IM_UPDATE) {
 | 
			
		||||
    ESP_LOGW(TAG, "Timeout loading NVM.");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed("Timeout loading NVM");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -153,26 +153,26 @@ void BME280Component::setup() {
 | 
			
		||||
 | 
			
		||||
  uint8_t humid_control_val = 0;
 | 
			
		||||
  if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed("Read humidity control");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  humid_control_val &= ~0b00000111;
 | 
			
		||||
  humid_control_val |= this->humidity_oversampling_ & 0b111;
 | 
			
		||||
  if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed("Write humidity control");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t config_register = 0;
 | 
			
		||||
  if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed("Read config");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  config_register &= ~0b11111100;
 | 
			
		||||
  config_register |= 0b101 << 5;  // 1000 ms standby time
 | 
			
		||||
  config_register |= (this->iir_filter_ & 0b111) << 2;
 | 
			
		||||
  if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed("Write config");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -183,7 +183,7 @@ void BME280Component::dump_config() {
 | 
			
		||||
      ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
 | 
			
		||||
      break;
 | 
			
		||||
    case WRONG_CHIP_ID:
 | 
			
		||||
      ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?");
 | 
			
		||||
      ESP_LOGE(TAG, BME280_ERROR_WRONG_CHIP_ID);
 | 
			
		||||
      break;
 | 
			
		||||
    case NONE:
 | 
			
		||||
    default:
 | 
			
		||||
@@ -223,21 +223,21 @@ void BME280Component::update() {
 | 
			
		||||
  this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
 | 
			
		||||
    uint8_t data[8];
 | 
			
		||||
    if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
 | 
			
		||||
      ESP_LOGW(TAG, "Error reading registers.");
 | 
			
		||||
      ESP_LOGW(TAG, "Error reading registers");
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    int32_t t_fine = 0;
 | 
			
		||||
    float const temperature = this->read_temperature_(data, &t_fine);
 | 
			
		||||
    if (std::isnan(temperature)) {
 | 
			
		||||
      ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
 | 
			
		||||
      ESP_LOGW(TAG, "Invalid temperature");
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    float const pressure = this->read_pressure_(data, t_fine);
 | 
			
		||||
    float const humidity = this->read_humidity_(data, t_fine);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
 | 
			
		||||
    ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa Humidity=%.1f%%", temperature, pressure, humidity);
 | 
			
		||||
    if (this->temperature_sensor_ != nullptr)
 | 
			
		||||
      this->temperature_sensor_->publish_state(temperature);
 | 
			
		||||
    if (this->pressure_sensor_ != nullptr)
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#define BMP280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bmp280_base {
 | 
			
		||||
 | 
			
		||||
@@ -63,23 +65,23 @@ void BMP280Component::setup() {
 | 
			
		||||
  // https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
 | 
			
		||||
  if (!this->read_byte(0xD0, &chip_id)) {
 | 
			
		||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->read_byte(0xD0, &chip_id)) {
 | 
			
		||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (chip_id != 0x58) {
 | 
			
		||||
    this->error_code_ = WRONG_CHIP_ID;
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed(BMP280_ERROR_WRONG_CHIP_ID);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Send a soft reset.
 | 
			
		||||
  if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed("Reset failed");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // Wait until the NVM data has finished loading.
 | 
			
		||||
@@ -88,14 +90,12 @@ void BMP280Component::setup() {
 | 
			
		||||
  do {
 | 
			
		||||
    delay(2);
 | 
			
		||||
    if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
 | 
			
		||||
      ESP_LOGW(TAG, "Error reading status register.");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      this->mark_failed("Error reading status register");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  } while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
 | 
			
		||||
  if (status & BMP280_STATUS_IM_UPDATE) {
 | 
			
		||||
    ESP_LOGW(TAG, "Timeout loading NVM.");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed("Timeout loading NVM");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -116,14 +116,14 @@ void BMP280Component::setup() {
 | 
			
		||||
 | 
			
		||||
  uint8_t config_register = 0;
 | 
			
		||||
  if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed("Read config");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  config_register &= ~0b11111100;
 | 
			
		||||
  config_register |= 0b000 << 5;  // 0.5 ms standby time
 | 
			
		||||
  config_register |= (this->iir_filter_ & 0b111) << 2;
 | 
			
		||||
  if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->mark_failed("Write config");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -134,7 +134,7 @@ void BMP280Component::dump_config() {
 | 
			
		||||
      ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
 | 
			
		||||
      break;
 | 
			
		||||
    case WRONG_CHIP_ID:
 | 
			
		||||
      ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BME280?");
 | 
			
		||||
      ESP_LOGE(TAG, BMP280_ERROR_WRONG_CHIP_ID);
 | 
			
		||||
      break;
 | 
			
		||||
    case NONE:
 | 
			
		||||
    default:
 | 
			
		||||
@@ -172,13 +172,13 @@ void BMP280Component::update() {
 | 
			
		||||
    int32_t t_fine = 0;
 | 
			
		||||
    float temperature = this->read_temperature_(&t_fine);
 | 
			
		||||
    if (std::isnan(temperature)) {
 | 
			
		||||
      ESP_LOGW(TAG, "Invalid temperature, cannot read pressure values.");
 | 
			
		||||
      ESP_LOGW(TAG, "Invalid temperature");
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    float pressure = this->read_pressure_(t_fine);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure);
 | 
			
		||||
    ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa", temperature, pressure);
 | 
			
		||||
    if (this->temperature_sensor_ != nullptr)
 | 
			
		||||
      this->temperature_sensor_->publish_state(temperature);
 | 
			
		||||
    if (this->pressure_sensor_ != nullptr)
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ void DelonghiClimate::transmit_state() {
 | 
			
		||||
 | 
			
		||||
  data->mark(DELONGHI_HEADER_MARK);
 | 
			
		||||
  data->space(DELONGHI_HEADER_SPACE);
 | 
			
		||||
  for (uint8_t b : remote_state) {
 | 
			
		||||
  for (unsigned char b : remote_state) {
 | 
			
		||||
    for (uint8_t mask = 1; mask > 0; mask <<= 1) {  // iterate through bit mask
 | 
			
		||||
      data->mark(DELONGHI_BIT_MARK);
 | 
			
		||||
      bool bit = b & mask;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#include "ble_uuid.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#ifdef USE_ESP32_BLE_UUID
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
@@ -190,4 +191,5 @@ std::string ESPBTUUID::to_string() const {
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome::esp32_ble
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP32_BLE_UUID
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#ifdef USE_ESP32_BLE_UUID
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
@@ -42,4 +44,5 @@ class ESPBTUUID {
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome::esp32_ble
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP32_BLE_UUID
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,8 @@ FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_define("USE_ESP32_BLE_UUID")
 | 
			
		||||
 | 
			
		||||
    uuid = config[CONF_UUID].hex
 | 
			
		||||
    uuid_arr = [
 | 
			
		||||
        cg.RawExpression(f"0x{uuid[i : i + 2]}") for i in range(0, len(uuid), 2)
 | 
			
		||||
 
 | 
			
		||||
@@ -529,6 +529,7 @@ async def to_code_characteristic(service_var, char_conf):
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    # Register the loggers this component needs
 | 
			
		||||
    esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
 | 
			
		||||
    cg.add_define("USE_ESP32_BLE_UUID")
 | 
			
		||||
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -373,6 +373,7 @@ async def _add_ble_features():
 | 
			
		||||
    # Add feature-specific defines based on what's needed
 | 
			
		||||
    if BLEFeatures.ESP_BT_DEVICE in _required_features:
 | 
			
		||||
        cg.add_define("USE_ESP32_BLE_DEVICE")
 | 
			
		||||
        cg.add_define("USE_ESP32_BLE_UUID")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
 
 | 
			
		||||
@@ -33,10 +33,12 @@ enum AdvertisementParserType {
 | 
			
		||||
  RAW_ADVERTISEMENTS,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_BLE_UUID
 | 
			
		||||
struct ServiceData {
 | 
			
		||||
  ESPBTUUID uuid;
 | 
			
		||||
  adv_data_t data;
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_BLE_DEVICE
 | 
			
		||||
class ESPBLEiBeacon {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,8 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(LD2450Component),
 | 
			
		||||
            cv.Optional(CONF_THROTTLE, default="1000ms"): cv.All(
 | 
			
		||||
                cv.positive_time_period_milliseconds,
 | 
			
		||||
                cv.Range(min=cv.TimePeriod(milliseconds=1)),
 | 
			
		||||
            cv.Optional(CONF_THROTTLE): cv.invalid(
 | 
			
		||||
                f"{CONF_THROTTLE} has been removed; use per-sensor filters, instead"
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
@@ -46,4 +45,3 @@ async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await uart.register_uart_device(var, config)
 | 
			
		||||
    cg.add(var.set_throttle(config[CONF_THROTTLE]))
 | 
			
		||||
 
 | 
			
		||||
@@ -21,14 +21,17 @@ CONFIG_SCHEMA = {
 | 
			
		||||
    cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
 | 
			
		||||
    cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
 | 
			
		||||
        device_class=DEVICE_CLASS_OCCUPANCY,
 | 
			
		||||
        filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
 | 
			
		||||
        icon=ICON_SHIELD_ACCOUNT,
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema(
 | 
			
		||||
        device_class=DEVICE_CLASS_MOTION,
 | 
			
		||||
        filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
 | 
			
		||||
        icon=ICON_TARGET_ACCOUNT,
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema(
 | 
			
		||||
        device_class=DEVICE_CLASS_OCCUPANCY,
 | 
			
		||||
        filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
 | 
			
		||||
        icon=ICON_MEDITATION,
 | 
			
		||||
    ),
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -199,9 +199,8 @@ void LD2450Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG,
 | 
			
		||||
                "LD2450:\n"
 | 
			
		||||
                "  Firmware version: %s\n"
 | 
			
		||||
                "  MAC address: %s\n"
 | 
			
		||||
                "  Throttle: %u ms",
 | 
			
		||||
                version.c_str(), mac_str.c_str(), this->throttle_);
 | 
			
		||||
                "  MAC address: %s",
 | 
			
		||||
                version.c_str(), mac_str.c_str());
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Binary Sensors:");
 | 
			
		||||
  LOG_BINARY_SENSOR("  ", "MovingTarget", this->moving_target_binary_sensor_);
 | 
			
		||||
@@ -431,11 +430,6 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu
 | 
			
		||||
//  [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC]
 | 
			
		||||
//   Header       Target 1                  Target 2                  Target 3                  End
 | 
			
		||||
void LD2450Component::handle_periodic_data_() {
 | 
			
		||||
  // Early throttle check - moved before any processing to save CPU cycles
 | 
			
		||||
  if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->buffer_pos_ < 29) {  // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
 | 
			
		||||
    ESP_LOGE(TAG, "Invalid length");
 | 
			
		||||
    return;
 | 
			
		||||
@@ -446,8 +440,6 @@ void LD2450Component::handle_periodic_data_() {
 | 
			
		||||
    ESP_LOGE(TAG, "Invalid header/footer");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // Save the timestamp after validating the frame so, if invalid, we'll take the next frame immediately
 | 
			
		||||
  this->last_periodic_millis_ = App.get_loop_component_start_time();
 | 
			
		||||
 | 
			
		||||
  int16_t target_count = 0;
 | 
			
		||||
  int16_t still_target_count = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,6 @@ class LD2450Component : public Component, public uart::UARTDevice {
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void set_presence_timeout();
 | 
			
		||||
  void set_throttle(uint16_t value) { this->throttle_ = value; }
 | 
			
		||||
  void read_all_info();
 | 
			
		||||
  void query_zone_info();
 | 
			
		||||
  void restart_and_read_all_info();
 | 
			
		||||
@@ -161,11 +160,9 @@ class LD2450Component : public Component, public uart::UARTDevice {
 | 
			
		||||
  bool get_timeout_status_(uint32_t check_millis);
 | 
			
		||||
  uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving);
 | 
			
		||||
 | 
			
		||||
  uint32_t last_periodic_millis_ = 0;
 | 
			
		||||
  uint32_t presence_millis_ = 0;
 | 
			
		||||
  uint32_t still_presence_millis_ = 0;
 | 
			
		||||
  uint32_t moving_presence_millis_ = 0;
 | 
			
		||||
  uint16_t throttle_ = 0;
 | 
			
		||||
  uint16_t timeout_ = 5;
 | 
			
		||||
  uint8_t buffer_data_[MAX_LINE_LENGTH];
 | 
			
		||||
  uint8_t mac_address_[6] = {0, 0, 0, 0, 0, 0};
 | 
			
		||||
 
 | 
			
		||||
@@ -42,16 +42,43 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
 | 
			
		||||
        cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema(
 | 
			
		||||
            icon=ICON_ACCOUNT_GROUP,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            filters=[
 | 
			
		||||
                {
 | 
			
		||||
                    "timeout": {
 | 
			
		||||
                        "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                        "value": "last",
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
            ],
 | 
			
		||||
            icon=ICON_ACCOUNT_GROUP,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema(
 | 
			
		||||
            icon=ICON_HUMAN_GREETING_PROXIMITY,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            filters=[
 | 
			
		||||
                {
 | 
			
		||||
                    "timeout": {
 | 
			
		||||
                        "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                        "value": "last",
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
            ],
 | 
			
		||||
            icon=ICON_HUMAN_GREETING_PROXIMITY,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema(
 | 
			
		||||
            icon=ICON_ACCOUNT_SWITCH,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            filters=[
 | 
			
		||||
                {
 | 
			
		||||
                    "timeout": {
 | 
			
		||||
                        "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                        "value": "last",
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
            ],
 | 
			
		||||
            icon=ICON_ACCOUNT_SWITCH,
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
@@ -62,32 +89,86 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_X): sensor.sensor_schema(
 | 
			
		||||
                    device_class=DEVICE_CLASS_DISTANCE,
 | 
			
		||||
                    unit_of_measurement=UNIT_MILLIMETER,
 | 
			
		||||
                    filters=[
 | 
			
		||||
                        {
 | 
			
		||||
                            "timeout": {
 | 
			
		||||
                                "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                                "value": "last",
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
                    ],
 | 
			
		||||
                    icon=ICON_ALPHA_X_BOX_OUTLINE,
 | 
			
		||||
                    unit_of_measurement=UNIT_MILLIMETER,
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_Y): sensor.sensor_schema(
 | 
			
		||||
                    device_class=DEVICE_CLASS_DISTANCE,
 | 
			
		||||
                    unit_of_measurement=UNIT_MILLIMETER,
 | 
			
		||||
                    filters=[
 | 
			
		||||
                        {
 | 
			
		||||
                            "timeout": {
 | 
			
		||||
                                "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                                "value": "last",
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
                    ],
 | 
			
		||||
                    icon=ICON_ALPHA_Y_BOX_OUTLINE,
 | 
			
		||||
                    unit_of_measurement=UNIT_MILLIMETER,
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_SPEED): sensor.sensor_schema(
 | 
			
		||||
                    device_class=DEVICE_CLASS_SPEED,
 | 
			
		||||
                    unit_of_measurement=UNIT_MILLIMETER_PER_SECOND,
 | 
			
		||||
                    filters=[
 | 
			
		||||
                        {
 | 
			
		||||
                            "timeout": {
 | 
			
		||||
                                "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                                "value": "last",
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
                    ],
 | 
			
		||||
                    icon=ICON_SPEEDOMETER_SLOW,
 | 
			
		||||
                    unit_of_measurement=UNIT_MILLIMETER_PER_SECOND,
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_ANGLE): sensor.sensor_schema(
 | 
			
		||||
                    unit_of_measurement=UNIT_DEGREES,
 | 
			
		||||
                    filters=[
 | 
			
		||||
                        {
 | 
			
		||||
                            "timeout": {
 | 
			
		||||
                                "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                                "value": "last",
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
                    ],
 | 
			
		||||
                    icon=ICON_FORMAT_TEXT_ROTATION_ANGLE_UP,
 | 
			
		||||
                    unit_of_measurement=UNIT_DEGREES,
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
 | 
			
		||||
                    device_class=DEVICE_CLASS_DISTANCE,
 | 
			
		||||
                    unit_of_measurement=UNIT_MILLIMETER,
 | 
			
		||||
                    filters=[
 | 
			
		||||
                        {
 | 
			
		||||
                            "timeout": {
 | 
			
		||||
                                "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                                "value": "last",
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
                    ],
 | 
			
		||||
                    icon=ICON_MAP_MARKER_DISTANCE,
 | 
			
		||||
                    unit_of_measurement=UNIT_MILLIMETER,
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_RESOLUTION): sensor.sensor_schema(
 | 
			
		||||
                    device_class=DEVICE_CLASS_DISTANCE,
 | 
			
		||||
                    unit_of_measurement=UNIT_MILLIMETER,
 | 
			
		||||
                    filters=[
 | 
			
		||||
                        {
 | 
			
		||||
                            "timeout": {
 | 
			
		||||
                                "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                                "value": "last",
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
                    ],
 | 
			
		||||
                    icon=ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE,
 | 
			
		||||
                    unit_of_measurement=UNIT_MILLIMETER,
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
@@ -97,16 +178,43 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
 | 
			
		||||
        cv.Optional(f"zone_{n + 1}"): cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema(
 | 
			
		||||
                    icon=ICON_MAP_MARKER_ACCOUNT,
 | 
			
		||||
                    accuracy_decimals=0,
 | 
			
		||||
                    filters=[
 | 
			
		||||
                        {
 | 
			
		||||
                            "timeout": {
 | 
			
		||||
                                "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                                "value": "last",
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
                    ],
 | 
			
		||||
                    icon=ICON_MAP_MARKER_ACCOUNT,
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema(
 | 
			
		||||
                    icon=ICON_MAP_MARKER_ACCOUNT,
 | 
			
		||||
                    accuracy_decimals=0,
 | 
			
		||||
                    filters=[
 | 
			
		||||
                        {
 | 
			
		||||
                            "timeout": {
 | 
			
		||||
                                "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                                "value": "last",
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
                    ],
 | 
			
		||||
                    icon=ICON_MAP_MARKER_ACCOUNT,
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema(
 | 
			
		||||
                    icon=ICON_MAP_MARKER_ACCOUNT,
 | 
			
		||||
                    accuracy_decimals=0,
 | 
			
		||||
                    filters=[
 | 
			
		||||
                        {
 | 
			
		||||
                            "timeout": {
 | 
			
		||||
                                "timeout": cv.TimePeriod(milliseconds=1000),
 | 
			
		||||
                                "value": "last",
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
 | 
			
		||||
                    ],
 | 
			
		||||
                    icon=ICON_MAP_MARKER_ACCOUNT,
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -110,10 +110,10 @@ static uint8_t find_nearest_index(float value, const float *arr, int size) {
 | 
			
		||||
 * @param value The float value to convert.
 | 
			
		||||
 * @param bytes The byte array to store the converted value.
 | 
			
		||||
 */
 | 
			
		||||
static void float_to_bytes(float value, uint8_t *bytes) {
 | 
			
		||||
static void float_to_bytes(float value, unsigned char *bytes) {
 | 
			
		||||
  union {
 | 
			
		||||
    float float_value;
 | 
			
		||||
    uint8_t byte_array[4];
 | 
			
		||||
    unsigned char byte_array[4];
 | 
			
		||||
  } u;
 | 
			
		||||
 | 
			
		||||
  u.float_value = value;
 | 
			
		||||
@@ -128,7 +128,7 @@ static void float_to_bytes(float value, uint8_t *bytes) {
 | 
			
		||||
 * @param value The 32-bit unsigned integer to convert.
 | 
			
		||||
 * @param bytes The byte array to store the converted value.
 | 
			
		||||
 */
 | 
			
		||||
static void int_to_bytes(uint32_t value, uint8_t *bytes) {
 | 
			
		||||
static void int_to_bytes(uint32_t value, unsigned char *bytes) {
 | 
			
		||||
  bytes[0] = value & 0xFF;
 | 
			
		||||
  bytes[1] = (value >> 8) & 0xFF;
 | 
			
		||||
  bytes[2] = (value >> 16) & 0xFF;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ namespace esphome {
 | 
			
		||||
namespace tm1638 {
 | 
			
		||||
namespace TM1638Translation {
 | 
			
		||||
 | 
			
		||||
const uint8_t SEVEN_SEG[] PROGMEM = {
 | 
			
		||||
const unsigned char SEVEN_SEG[] PROGMEM = {
 | 
			
		||||
    0x00, /* (space) */
 | 
			
		||||
    0x86, /* ! */
 | 
			
		||||
    0x22, /* " */
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ from enum import Enum
 | 
			
		||||
 | 
			
		||||
from esphome.enum import StrEnum
 | 
			
		||||
 | 
			
		||||
__version__ = "2025.8.0-dev"
 | 
			
		||||
__version__ = "2025.8.0b1"
 | 
			
		||||
 | 
			
		||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 | 
			
		||||
VALID_SUBSTITUTIONS_CHARACTERS = (
 | 
			
		||||
 
 | 
			
		||||
@@ -154,6 +154,7 @@
 | 
			
		||||
#define USE_ESP32_BLE_CLIENT
 | 
			
		||||
#define USE_ESP32_BLE_DEVICE
 | 
			
		||||
#define USE_ESP32_BLE_SERVER
 | 
			
		||||
#define USE_ESP32_BLE_UUID
 | 
			
		||||
#define USE_ESP32_BLE_ADVERTISING
 | 
			
		||||
#define USE_I2C
 | 
			
		||||
#define USE_IMPROV
 | 
			
		||||
 
 | 
			
		||||
@@ -139,9 +139,24 @@ def _get_changed_files_github_actions() -> list[str] | None:
 | 
			
		||||
    if event_name == "pull_request":
 | 
			
		||||
        pr_number = _get_pr_number_from_github_env()
 | 
			
		||||
        if pr_number:
 | 
			
		||||
            # Use GitHub CLI to get changed files directly
 | 
			
		||||
            # Try gh pr diff first (faster for small PRs)
 | 
			
		||||
            cmd = ["gh", "pr", "diff", pr_number, "--name-only"]
 | 
			
		||||
            return _get_changed_files_from_command(cmd)
 | 
			
		||||
            try:
 | 
			
		||||
                return _get_changed_files_from_command(cmd)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                # If it fails due to the 300 file limit, use the API method
 | 
			
		||||
                if "maximum" in str(e) and "files" in str(e):
 | 
			
		||||
                    cmd = [
 | 
			
		||||
                        "gh",
 | 
			
		||||
                        "api",
 | 
			
		||||
                        f"repos/esphome/esphome/pulls/{pr_number}/files",
 | 
			
		||||
                        "--paginate",
 | 
			
		||||
                        "--jq",
 | 
			
		||||
                        ".[].filename",
 | 
			
		||||
                    ]
 | 
			
		||||
                    return _get_changed_files_from_command(cmd)
 | 
			
		||||
                # Re-raise for other errors
 | 
			
		||||
                raise
 | 
			
		||||
 | 
			
		||||
    # For pushes (including squash-and-merge)
 | 
			
		||||
    elif event_name == "push":
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ uart:
 | 
			
		||||
ld2450:
 | 
			
		||||
  - id: ld2450_radar
 | 
			
		||||
    uart_id: ld2450_uart
 | 
			
		||||
    throttle: 1000ms
 | 
			
		||||
 | 
			
		||||
button:
 | 
			
		||||
  - platform: ld2450
 | 
			
		||||
 
 | 
			
		||||
@@ -183,6 +183,61 @@ def test_get_changed_files_github_actions_pull_request(
 | 
			
		||||
        assert result == expected_files
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_changed_files_github_actions_pull_request_large_pr(
 | 
			
		||||
    monkeypatch: MonkeyPatch,
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Test _get_changed_files_github_actions fallback for PRs with >300 files."""
 | 
			
		||||
    monkeypatch.setenv("GITHUB_EVENT_NAME", "pull_request")
 | 
			
		||||
 | 
			
		||||
    expected_files = ["file1.py", "file2.cpp"]
 | 
			
		||||
 | 
			
		||||
    with (
 | 
			
		||||
        patch("helpers._get_pr_number_from_github_env", return_value="10214"),
 | 
			
		||||
        patch("helpers._get_changed_files_from_command") as mock_get,
 | 
			
		||||
    ):
 | 
			
		||||
        # First call fails with too many files error, second succeeds with API method
 | 
			
		||||
        mock_get.side_effect = [
 | 
			
		||||
            Exception("Sorry, the diff exceeded the maximum number of files (300)"),
 | 
			
		||||
            expected_files,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        result = _get_changed_files_github_actions()
 | 
			
		||||
 | 
			
		||||
        assert mock_get.call_count == 2
 | 
			
		||||
        mock_get.assert_any_call(["gh", "pr", "diff", "10214", "--name-only"])
 | 
			
		||||
        mock_get.assert_any_call(
 | 
			
		||||
            [
 | 
			
		||||
                "gh",
 | 
			
		||||
                "api",
 | 
			
		||||
                "repos/esphome/esphome/pulls/10214/files",
 | 
			
		||||
                "--paginate",
 | 
			
		||||
                "--jq",
 | 
			
		||||
                ".[].filename",
 | 
			
		||||
            ]
 | 
			
		||||
        )
 | 
			
		||||
        assert result == expected_files
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_changed_files_github_actions_pull_request_other_error(
 | 
			
		||||
    monkeypatch: MonkeyPatch,
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Test _get_changed_files_github_actions re-raises non-file-limit errors."""
 | 
			
		||||
    monkeypatch.setenv("GITHUB_EVENT_NAME", "pull_request")
 | 
			
		||||
 | 
			
		||||
    with (
 | 
			
		||||
        patch("helpers._get_pr_number_from_github_env", return_value="1234"),
 | 
			
		||||
        patch("helpers._get_changed_files_from_command") as mock_get,
 | 
			
		||||
    ):
 | 
			
		||||
        # Error that is not about file limit
 | 
			
		||||
        mock_get.side_effect = Exception("Command failed: authentication required")
 | 
			
		||||
 | 
			
		||||
        with pytest.raises(Exception, match="authentication required"):
 | 
			
		||||
            _get_changed_files_github_actions()
 | 
			
		||||
 | 
			
		||||
        # Should only be called once (no retry with API)
 | 
			
		||||
        mock_get.assert_called_once_with(["gh", "pr", "diff", "1234", "--name-only"])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_changed_files_github_actions_pull_request_no_pr_number(
 | 
			
		||||
    monkeypatch: MonkeyPatch,
 | 
			
		||||
) -> None:
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user