mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Set ble tx power
This commit is contained in:
		| @@ -3,7 +3,17 @@ import re | |||||||
|  |  | ||||||
| from esphome import automation | from esphome import automation | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant | from esphome.components.esp32 import ( | ||||||
|  |     VARIANT_ESP32C2, | ||||||
|  |     VARIANT_ESP32C3, | ||||||
|  |     VARIANT_ESP32C5, | ||||||
|  |     VARIANT_ESP32C6, | ||||||
|  |     VARIANT_ESP32H2, | ||||||
|  |     VARIANT_ESP32S3, | ||||||
|  |     add_idf_sdkconfig_option, | ||||||
|  |     const, | ||||||
|  |     get_esp32_variant, | ||||||
|  | ) | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ENABLE_ON_BOOT, |     CONF_ENABLE_ON_BOOT, | ||||||
| @@ -11,8 +21,10 @@ from esphome.const import ( | |||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_NAME, |     CONF_NAME, | ||||||
|     CONF_NAME_ADD_MAC_SUFFIX, |     CONF_NAME_ADD_MAC_SUFFIX, | ||||||
|  |     CONF_TX_POWER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, TimePeriod | from esphome.core import CORE, TimePeriod | ||||||
|  | from esphome.cpp_types import MockObj | ||||||
| import esphome.final_validate as fv | import esphome.final_validate as fv | ||||||
|  |  | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
| @@ -151,7 +163,8 @@ IO_CAPABILITY = { | |||||||
|  |  | ||||||
| esp_power_level_t = cg.global_ns.enum("esp_power_level_t") | esp_power_level_t = cg.global_ns.enum("esp_power_level_t") | ||||||
|  |  | ||||||
| TX_POWER_LEVELS = { | # Power level mappings for code generation - ESP32 classic | ||||||
|  | TX_POWER_LEVELS_ESP32 = { | ||||||
|     -12: esp_power_level_t.ESP_PWR_LVL_N12, |     -12: esp_power_level_t.ESP_PWR_LVL_N12, | ||||||
|     -9: esp_power_level_t.ESP_PWR_LVL_N9, |     -9: esp_power_level_t.ESP_PWR_LVL_N9, | ||||||
|     -6: esp_power_level_t.ESP_PWR_LVL_N6, |     -6: esp_power_level_t.ESP_PWR_LVL_N6, | ||||||
| @@ -162,6 +175,53 @@ TX_POWER_LEVELS = { | |||||||
|     9: esp_power_level_t.ESP_PWR_LVL_P9, |     9: esp_power_level_t.ESP_PWR_LVL_P9, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | # Power level mappings for code generation - Extended variants | ||||||
|  | TX_POWER_LEVELS_EXT = { | ||||||
|  |     -24: esp_power_level_t.ESP_PWR_LVL_N24, | ||||||
|  |     -21: esp_power_level_t.ESP_PWR_LVL_N21, | ||||||
|  |     -18: esp_power_level_t.ESP_PWR_LVL_N18, | ||||||
|  |     -15: esp_power_level_t.ESP_PWR_LVL_N15, | ||||||
|  |     -12: esp_power_level_t.ESP_PWR_LVL_N12, | ||||||
|  |     -9: esp_power_level_t.ESP_PWR_LVL_N9, | ||||||
|  |     -6: esp_power_level_t.ESP_PWR_LVL_N6, | ||||||
|  |     -3: esp_power_level_t.ESP_PWR_LVL_N3, | ||||||
|  |     0: esp_power_level_t.ESP_PWR_LVL_N0, | ||||||
|  |     3: esp_power_level_t.ESP_PWR_LVL_P3, | ||||||
|  |     6: esp_power_level_t.ESP_PWR_LVL_P6, | ||||||
|  |     9: esp_power_level_t.ESP_PWR_LVL_P9, | ||||||
|  |     12: esp_power_level_t.ESP_PWR_LVL_P12, | ||||||
|  |     15: esp_power_level_t.ESP_PWR_LVL_P15, | ||||||
|  |     18: esp_power_level_t.ESP_PWR_LVL_P18, | ||||||
|  |     20: esp_power_level_t.ESP_PWR_LVL_P20, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _get_tx_power_levels() -> dict[str, MockObj]: | ||||||
|  |     variant = get_esp32_variant() | ||||||
|  |     if variant in [ | ||||||
|  |         VARIANT_ESP32C2, | ||||||
|  |         VARIANT_ESP32C3, | ||||||
|  |         VARIANT_ESP32C5, | ||||||
|  |         VARIANT_ESP32C6, | ||||||
|  |         VARIANT_ESP32H2, | ||||||
|  |         VARIANT_ESP32S3, | ||||||
|  |     ]: | ||||||
|  |         return TX_POWER_LEVELS_EXT | ||||||
|  |     return TX_POWER_LEVELS_ESP32 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_tx_power(value: int) -> int: | ||||||
|  |     value = cv.decibel(value) | ||||||
|  |     power_levels = _get_tx_power_levels() | ||||||
|  |     if value not in power_levels: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f"TX power {value}dBm is not valid. " | ||||||
|  |             f"Valid values are: {', '.join(str(v) + 'dBm' for v in sorted(power_levels.keys()))}" | ||||||
|  |         ) | ||||||
|  |     # Return just the dBm value, we'll map it to enum in to_code | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema( | CONFIG_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(ESP32BLE), |         cv.GenerateID(): cv.declare_id(ESP32BLE), | ||||||
| @@ -169,6 +229,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|         cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( |         cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( | ||||||
|             IO_CAPABILITY, lower=True |             IO_CAPABILITY, lower=True | ||||||
|         ), |         ), | ||||||
|  |         cv.Optional(CONF_TX_POWER): validate_tx_power, | ||||||
|         cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, |         cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, | ||||||
|         cv.Optional(CONF_ADVERTISING, default=False): cv.boolean, |         cv.Optional(CONF_ADVERTISING, default=False): cv.boolean, | ||||||
|         cv.Optional( |         cv.Optional( | ||||||
| @@ -259,6 +320,9 @@ async def to_code(config): | |||||||
|     cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME])) |     cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME])) | ||||||
|     if (name := config.get(CONF_NAME)) is not None: |     if (name := config.get(CONF_NAME)) is not None: | ||||||
|         cg.add(var.set_name(name)) |         cg.add(var.set_name(name)) | ||||||
|  |     if (tx_power := config.get(CONF_TX_POWER)) is not None: | ||||||
|  |         # The validation already returned the enum value | ||||||
|  |         cg.add(var.set_tx_power(_get_tx_power_levels()[tx_power])) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|     if CORE.using_esp_idf: |     if CORE.using_esp_idf: | ||||||
|   | |||||||
| @@ -212,6 +212,15 @@ bool ESP32BLE::ble_setup_() { | |||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Set TX power for all BLE operations (advertising, scanning, connections) | ||||||
|  |   err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, this->tx_power_); | ||||||
|  |   if (err != ESP_OK) { | ||||||
|  |     ESP_LOGW(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err)); | ||||||
|  |     // Continue anyway as this is not critical | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGD(TAG, "BLE TX power set to level %d", this->tx_power_); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // BLE takes some time to be fully set up, 200ms should be more than enough |   // BLE takes some time to be fully set up, 200ms should be more than enough | ||||||
|   delay(200);  // NOLINT |   delay(200);  // NOLINT | ||||||
|  |  | ||||||
| @@ -520,11 +529,106 @@ void ESP32BLE::dump_config() { | |||||||
|         io_capability_s = "invalid"; |         io_capability_s = "invalid"; | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  |     // Convert TX power level to dBm for display | ||||||
|  |     int tx_power_dbm = 0; | ||||||
|  | #if defined(CONFIG_IDF_TARGET_ESP32) | ||||||
|  |     // ESP32 classic power levels (0-7) | ||||||
|  |     switch (this->tx_power_) { | ||||||
|  |       case 0: | ||||||
|  |         tx_power_dbm = -12; | ||||||
|  |         break;  // ESP_PWR_LVL_N12 | ||||||
|  |       case 1: | ||||||
|  |         tx_power_dbm = -9; | ||||||
|  |         break;  // ESP_PWR_LVL_N9 | ||||||
|  |       case 2: | ||||||
|  |         tx_power_dbm = -6; | ||||||
|  |         break;  // ESP_PWR_LVL_N6 | ||||||
|  |       case 3: | ||||||
|  |         tx_power_dbm = -3; | ||||||
|  |         break;  // ESP_PWR_LVL_N3 | ||||||
|  |       case 4: | ||||||
|  |         tx_power_dbm = 0; | ||||||
|  |         break;  // ESP_PWR_LVL_N0 | ||||||
|  |       case 5: | ||||||
|  |         tx_power_dbm = 3; | ||||||
|  |         break;  // ESP_PWR_LVL_P3 | ||||||
|  |       case 6: | ||||||
|  |         tx_power_dbm = 6; | ||||||
|  |         break;  // ESP_PWR_LVL_P6 | ||||||
|  |       case 7: | ||||||
|  |         tx_power_dbm = 9; | ||||||
|  |         break;  // ESP_PWR_LVL_P9 | ||||||
|  |       default: | ||||||
|  |         tx_power_dbm = 0; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | #elif defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || \ | ||||||
|  |     defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || \ | ||||||
|  |     defined(CONFIG_IDF_TARGET_ESP32S3) | ||||||
|  |     // Extended power levels for C2/C3/C5/C6/H2/S3 (0-15) | ||||||
|  |     switch (this->tx_power_) { | ||||||
|  |       case 0: | ||||||
|  |         tx_power_dbm = -24; | ||||||
|  |         break;  // ESP_PWR_LVL_N24 | ||||||
|  |       case 1: | ||||||
|  |         tx_power_dbm = -21; | ||||||
|  |         break;  // ESP_PWR_LVL_N21 | ||||||
|  |       case 2: | ||||||
|  |         tx_power_dbm = -18; | ||||||
|  |         break;  // ESP_PWR_LVL_N18 | ||||||
|  |       case 3: | ||||||
|  |         tx_power_dbm = -15; | ||||||
|  |         break;  // ESP_PWR_LVL_N15 | ||||||
|  |       case 4: | ||||||
|  |         tx_power_dbm = -12; | ||||||
|  |         break;  // ESP_PWR_LVL_N12 | ||||||
|  |       case 5: | ||||||
|  |         tx_power_dbm = -9; | ||||||
|  |         break;  // ESP_PWR_LVL_N9 | ||||||
|  |       case 6: | ||||||
|  |         tx_power_dbm = -6; | ||||||
|  |         break;  // ESP_PWR_LVL_N6 | ||||||
|  |       case 7: | ||||||
|  |         tx_power_dbm = -3; | ||||||
|  |         break;  // ESP_PWR_LVL_N3 | ||||||
|  |       case 8: | ||||||
|  |         tx_power_dbm = 0; | ||||||
|  |         break;  // ESP_PWR_LVL_N0 | ||||||
|  |       case 9: | ||||||
|  |         tx_power_dbm = 3; | ||||||
|  |         break;  // ESP_PWR_LVL_P3 | ||||||
|  |       case 10: | ||||||
|  |         tx_power_dbm = 6; | ||||||
|  |         break;  // ESP_PWR_LVL_P6 | ||||||
|  |       case 11: | ||||||
|  |         tx_power_dbm = 9; | ||||||
|  |         break;  // ESP_PWR_LVL_P9 | ||||||
|  |       case 12: | ||||||
|  |         tx_power_dbm = 12; | ||||||
|  |         break;  // ESP_PWR_LVL_P12 | ||||||
|  |       case 13: | ||||||
|  |         tx_power_dbm = 15; | ||||||
|  |         break;  // ESP_PWR_LVL_P15 | ||||||
|  |       case 14: | ||||||
|  |         tx_power_dbm = 18; | ||||||
|  |         break;  // ESP_PWR_LVL_P18 | ||||||
|  |       case 15: | ||||||
|  |         tx_power_dbm = 20; | ||||||
|  |         break;  // ESP_PWR_LVL_P20 | ||||||
|  |       default: | ||||||
|  |         tx_power_dbm = 0; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | #else | ||||||
|  |     // Unknown variant | ||||||
|  |     tx_power_dbm = 0; | ||||||
|  | #endif | ||||||
|     ESP_LOGCONFIG(TAG, |     ESP_LOGCONFIG(TAG, | ||||||
|                   "BLE:\n" |                   "BLE:\n" | ||||||
|                   "  MAC address: %s\n" |                   "  MAC address: %s\n" | ||||||
|                   "  IO Capability: %s", |                   "  IO Capability: %s\n" | ||||||
|                   format_mac_address_pretty(mac_address).c_str(), io_capability_s); |                   "  TX Power: %d dBm", | ||||||
|  |                   format_mac_address_pretty(mac_address).c_str(), io_capability_s, tx_power_dbm); | ||||||
|   } else { |   } else { | ||||||
|     ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled"); |     ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled"); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include <esp_bt.h> | ||||||
| #include <esp_gap_ble_api.h> | #include <esp_gap_ble_api.h> | ||||||
| #include <esp_gattc_api.h> | #include <esp_gattc_api.h> | ||||||
| #include <esp_gatts_api.h> | #include <esp_gatts_api.h> | ||||||
| @@ -94,6 +95,7 @@ class BLEStatusEventHandler { | |||||||
| class ESP32BLE : public Component { | class ESP32BLE : public Component { | ||||||
|  public: |  public: | ||||||
|   void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } |   void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } | ||||||
|  |   void set_tx_power(esp_power_level_t tx_power) { this->tx_power_ = tx_power; } | ||||||
|  |  | ||||||
|   void set_advertising_cycle_time(uint32_t advertising_cycle_time) { |   void set_advertising_cycle_time(uint32_t advertising_cycle_time) { | ||||||
|     this->advertising_cycle_time_ = advertising_cycle_time; |     this->advertising_cycle_time_ = advertising_cycle_time; | ||||||
| @@ -172,6 +174,7 @@ class ESP32BLE : public Component { | |||||||
|   // 1-byte aligned members (grouped together to minimize padding) |   // 1-byte aligned members (grouped together to minimize padding) | ||||||
|   BLEComponentState state_{BLE_COMPONENT_STATE_OFF};  // 1 byte (uint8_t enum) |   BLEComponentState state_{BLE_COMPONENT_STATE_OFF};  // 1 byte (uint8_t enum) | ||||||
|   bool enable_on_boot_{};                             // 1 byte |   bool enable_on_boot_{};                             // 1 byte | ||||||
|  |   esp_power_level_t tx_power_{ESP_PWR_LVL_P9};        // 1 byte (default: +9 dBm) | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user