mirror of
https://github.com/esphome/esphome.git
synced 2025-04-14 06:40:32 +01:00
Rework max connections for BLE to avoid exceeding the hard limit (#8303)
This commit is contained in:
parent
f3b1b11eba
commit
23e5cdb30e
@ -67,7 +67,7 @@ CONF_AUTO_CONNECT = "auto_connect"
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEClient),
|
||||
@ -114,7 +114,8 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA),
|
||||
esp32_ble_tracker.consume_connection_slots(1, "ble_client"),
|
||||
)
|
||||
|
||||
CONF_BLE_CLIENT_ID = "ble_client_id"
|
||||
|
@ -8,9 +8,10 @@ AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
||||
DEPENDENCIES = ["api", "esp32"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
||||
CONF_CONNECTION_SLOTS = "connection_slots"
|
||||
CONF_CACHE_SERVICES = "cache_services"
|
||||
CONF_CONNECTIONS = "connections"
|
||||
MAX_CONNECTIONS = 3
|
||||
DEFAULT_CONNECTION_SLOTS = 3
|
||||
|
||||
bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy")
|
||||
|
||||
@ -35,28 +36,42 @@ def validate_connections(config):
|
||||
"Connections can only be used if the proxy is set to active"
|
||||
)
|
||||
elif config[CONF_ACTIVE]:
|
||||
conf = config.copy()
|
||||
conf[CONF_CONNECTIONS] = [CONNECTION_SCHEMA({}) for _ in range(MAX_CONNECTIONS)]
|
||||
return conf
|
||||
connection_slots: int = config[CONF_CONNECTION_SLOTS]
|
||||
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")(
|
||||
config
|
||||
)
|
||||
return {
|
||||
**config,
|
||||
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
|
||||
}
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
||||
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
|
||||
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
|
||||
cv.only_with_esp_idf, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_CONNECTIONS): cv.All(
|
||||
cv.ensure_list(CONNECTION_SCHEMA),
|
||||
cv.Length(min=1, max=MAX_CONNECTIONS),
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
||||
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
|
||||
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
|
||||
cv.only_with_esp_idf, cv.boolean
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CONNECTION_SLOTS,
|
||||
default=DEFAULT_CONNECTION_SLOTS,
|
||||
): cv.All(
|
||||
cv.positive_int,
|
||||
cv.Range(min=1, max=esp32_ble_tracker.max_connections()),
|
||||
),
|
||||
cv.Optional(CONF_CONNECTIONS): cv.All(
|
||||
cv.ensure_list(CONNECTION_SCHEMA),
|
||||
cv.Length(min=1, max=esp32_ble_tracker.max_connections()),
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
),
|
||||
validate_connections,
|
||||
)
|
||||
|
||||
|
@ -1,3 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import MutableMapping
|
||||
import logging
|
||||
from typing import Any, Callable
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32_ble
|
||||
@ -29,11 +35,21 @@ from esphome.core import CORE
|
||||
AUTO_LOAD = ["esp32_ble"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
KEY_ESP32_BLE_TRACKER = "esp32_ble_tracker"
|
||||
KEY_USED_CONNECTION_SLOTS = "used_connection_slots"
|
||||
|
||||
CONF_MAX_CONNECTIONS = "max_connections"
|
||||
CONF_ESP32_BLE_ID = "esp32_ble_id"
|
||||
CONF_SCAN_PARAMETERS = "scan_parameters"
|
||||
CONF_WINDOW = "window"
|
||||
CONF_CONTINUOUS = "continuous"
|
||||
CONF_ON_SCAN_END = "on_scan_end"
|
||||
|
||||
DEFAULT_MAX_CONNECTIONS = 3
|
||||
IDF_MAX_CONNECTIONS = 9
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
|
||||
ESP32BLETracker = esp32_ble_tracker_ns.class_(
|
||||
"ESP32BLETracker",
|
||||
@ -112,61 +128,126 @@ def as_reversed_hex_array(value):
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32BLETracker),
|
||||
cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
|
||||
cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All(
|
||||
cv.Schema(
|
||||
def max_connections() -> int:
|
||||
return IDF_MAX_CONNECTIONS if CORE.using_esp_idf else DEFAULT_MAX_CONNECTIONS
|
||||
|
||||
|
||||
def consume_connection_slots(
|
||||
value: int, consumer: str
|
||||
) -> Callable[[MutableMapping], MutableMapping]:
|
||||
def _consume_connection_slots(config: MutableMapping) -> MutableMapping:
|
||||
data: dict[str, Any] = CORE.data.setdefault(KEY_ESP32_BLE_TRACKER, {})
|
||||
slots: list[str] = data.setdefault(KEY_USED_CONNECTION_SLOTS, [])
|
||||
slots.extend([consumer] * value)
|
||||
return config
|
||||
|
||||
return _consume_connection_slots
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32BLETracker),
|
||||
cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
|
||||
cv.Optional(CONF_MAX_CONNECTIONS, default=DEFAULT_MAX_CONNECTIONS): cv.All(
|
||||
cv.positive_int, cv.Range(min=0, max=max_connections())
|
||||
),
|
||||
cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_DURATION, default="5min"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Optional(
|
||||
CONF_INTERVAL, default="320ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_WINDOW, default="30ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_CONTINUOUS, default=True): cv.boolean,
|
||||
}
|
||||
),
|
||||
validate_scan_parameters,
|
||||
),
|
||||
cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_DURATION, default="5min"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Optional(
|
||||
CONF_INTERVAL, default="320ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_WINDOW, default="30ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_CONTINUOUS, default=True): cv.boolean,
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
ESPBTAdvertiseTrigger
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address),
|
||||
}
|
||||
),
|
||||
validate_scan_parameters,
|
||||
),
|
||||
cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEServiceDataAdvertiseTrigger
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Required(CONF_SERVICE_UUID): bt_uuid,
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE
|
||||
): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEManufacturerDataAdvertiseTrigger
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Required(CONF_MANUFACTURER_ID): bt_uuid,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SCAN_END): automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
cv.Optional(
|
||||
CONF_ON_BLE_SERVICE_DATA_ADVERTISE
|
||||
): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEServiceDataAdvertiseTrigger
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Required(CONF_SERVICE_UUID): bt_uuid,
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE
|
||||
): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEManufacturerDataAdvertiseTrigger
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Required(CONF_MANUFACTURER_ID): bt_uuid,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SCAN_END): automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
|
||||
|
||||
def validate_remaining_connections(config):
|
||||
data: dict[str, Any] = CORE.data.get(KEY_ESP32_BLE_TRACKER, {})
|
||||
slots: list[str] = data.get(KEY_USED_CONNECTION_SLOTS, [])
|
||||
used_slots = len(slots)
|
||||
if used_slots <= config[CONF_MAX_CONNECTIONS]:
|
||||
return config
|
||||
slot_users = ", ".join(slots)
|
||||
hard_limit = max_connections()
|
||||
|
||||
if used_slots < hard_limit:
|
||||
_LOGGER.warning(
|
||||
"esp32_ble_tracker exceeded `%s`: components attempted to consume %d "
|
||||
"connection slot(s) out of available configured maximum %d connection "
|
||||
"slot(s); The system automatically increased `%s` to %d to match the "
|
||||
"number of used connection slot(s) by components: %s.",
|
||||
CONF_MAX_CONNECTIONS,
|
||||
used_slots,
|
||||
config[CONF_MAX_CONNECTIONS],
|
||||
CONF_MAX_CONNECTIONS,
|
||||
used_slots,
|
||||
slot_users,
|
||||
)
|
||||
config[CONF_MAX_CONNECTIONS] = used_slots
|
||||
return config
|
||||
|
||||
msg = (
|
||||
f"esp32_ble_tracker exceeded `{CONF_MAX_CONNECTIONS}`: "
|
||||
f"components attempted to consume {used_slots} connection slot(s) "
|
||||
f"out of available configured maximum {config[CONF_MAX_CONNECTIONS]} "
|
||||
f"connection slot(s); Decrease the number of BLE clients ({slot_users})"
|
||||
)
|
||||
if config[CONF_MAX_CONNECTIONS] < hard_limit:
|
||||
msg += f" or increase {CONF_MAX_CONNECTIONS}` to {used_slots}"
|
||||
msg += f" to stay under the {hard_limit} connection slot(s) limit."
|
||||
raise cv.Invalid(msg)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
validate_remaining_connections, esp32_ble.validate_variant
|
||||
)
|
||||
|
||||
ESP_BLE_DEVICE_SCHEMA = cv.Schema(
|
||||
{
|
||||
@ -238,6 +319,9 @@ async def to_code(config):
|
||||
else:
|
||||
add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192)
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS]
|
||||
)
|
||||
# CONFIG_BT_GATTC_NOTIF_REG_MAX controls the number of
|
||||
# max notifications in 5.x, setting CONFIG_BT_ACL_CONNECTIONS
|
||||
# is enough in 4.x
|
||||
|
8
tests/components/bluetooth_proxy/common.yaml
Normal file
8
tests/components/bluetooth_proxy/common.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
api:
|
8
tests/components/bluetooth_proxy/test.esp32-ard.yaml
Normal file
8
tests/components/bluetooth_proxy/test.esp32-ard.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
esp32_ble_tracker:
|
||||
max_connections: 3
|
||||
|
||||
bluetooth_proxy:
|
||||
active: true
|
||||
connection_slots: 2
|
8
tests/components/bluetooth_proxy/test.esp32-c3-ard.yaml
Normal file
8
tests/components/bluetooth_proxy/test.esp32-c3-ard.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
esp32_ble_tracker:
|
||||
max_connections: 3
|
||||
|
||||
bluetooth_proxy:
|
||||
active: true
|
||||
connection_slots: 2
|
8
tests/components/bluetooth_proxy/test.esp32-c3-idf.yaml
Normal file
8
tests/components/bluetooth_proxy/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
esp32_ble_tracker:
|
||||
max_connections: 9
|
||||
|
||||
bluetooth_proxy:
|
||||
active: true
|
||||
connection_slots: 9
|
8
tests/components/bluetooth_proxy/test.esp32-idf.yaml
Normal file
8
tests/components/bluetooth_proxy/test.esp32-idf.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
esp32_ble_tracker:
|
||||
max_connections: 9
|
||||
|
||||
bluetooth_proxy:
|
||||
active: true
|
||||
connection_slots: 9
|
@ -1 +1,4 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
esp32_ble_tracker:
|
||||
max_connections: 3
|
||||
|
@ -1 +1,4 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
esp32_ble_tracker:
|
||||
max_connections: 3
|
||||
|
@ -1 +1,4 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
esp32_ble_tracker:
|
||||
max_connections: 9
|
||||
|
@ -1 +1,4 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
esp32_ble_tracker:
|
||||
max_connections: 9
|
||||
|
Loading…
x
Reference in New Issue
Block a user