mirror of
https://github.com/esphome/esphome.git
synced 2025-04-15 15:20:27 +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
|
MULTI_CONF = True
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(BLEClient),
|
cv.GenerateID(): cv.declare_id(BLEClient),
|
||||||
@ -114,7 +114,8 @@ CONFIG_SCHEMA = (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.COMPONENT_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"
|
CONF_BLE_CLIENT_ID = "ble_client_id"
|
||||||
|
@ -8,9 +8,10 @@ AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
|||||||
DEPENDENCIES = ["api", "esp32"]
|
DEPENDENCIES = ["api", "esp32"]
|
||||||
CODEOWNERS = ["@jesserockz"]
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
|
||||||
|
CONF_CONNECTION_SLOTS = "connection_slots"
|
||||||
CONF_CACHE_SERVICES = "cache_services"
|
CONF_CACHE_SERVICES = "cache_services"
|
||||||
CONF_CONNECTIONS = "connections"
|
CONF_CONNECTIONS = "connections"
|
||||||
MAX_CONNECTIONS = 3
|
DEFAULT_CONNECTION_SLOTS = 3
|
||||||
|
|
||||||
bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy")
|
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"
|
"Connections can only be used if the proxy is set to active"
|
||||||
)
|
)
|
||||||
elif config[CONF_ACTIVE]:
|
elif config[CONF_ACTIVE]:
|
||||||
conf = config.copy()
|
connection_slots: int = config[CONF_CONNECTION_SLOTS]
|
||||||
conf[CONF_CONNECTIONS] = [CONNECTION_SCHEMA({}) for _ in range(MAX_CONNECTIONS)]
|
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")(
|
||||||
return conf
|
config
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
**config,
|
||||||
|
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
|
||||||
|
}
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
(
|
||||||
{
|
cv.Schema(
|
||||||
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
{
|
||||||
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
|
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
||||||
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
|
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
|
||||||
cv.only_with_esp_idf, 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.Optional(
|
||||||
cv.Length(min=1, max=MAX_CONNECTIONS),
|
CONF_CONNECTION_SLOTS,
|
||||||
),
|
default=DEFAULT_CONNECTION_SLOTS,
|
||||||
}
|
): cv.All(
|
||||||
)
|
cv.positive_int,
|
||||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
cv.Range(min=1, max=esp32_ble_tracker.max_connections()),
|
||||||
.extend(cv.COMPONENT_SCHEMA),
|
),
|
||||||
|
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,
|
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
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32_ble
|
from esphome.components import esp32_ble
|
||||||
@ -29,11 +35,21 @@ from esphome.core import CORE
|
|||||||
AUTO_LOAD = ["esp32_ble"]
|
AUTO_LOAD = ["esp32_ble"]
|
||||||
DEPENDENCIES = ["esp32"]
|
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_ESP32_BLE_ID = "esp32_ble_id"
|
||||||
CONF_SCAN_PARAMETERS = "scan_parameters"
|
CONF_SCAN_PARAMETERS = "scan_parameters"
|
||||||
CONF_WINDOW = "window"
|
CONF_WINDOW = "window"
|
||||||
CONF_CONTINUOUS = "continuous"
|
CONF_CONTINUOUS = "continuous"
|
||||||
CONF_ON_SCAN_END = "on_scan_end"
|
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")
|
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
|
||||||
ESP32BLETracker = esp32_ble_tracker_ns.class_(
|
ESP32BLETracker = esp32_ble_tracker_ns.class_(
|
||||||
"ESP32BLETracker",
|
"ESP32BLETracker",
|
||||||
@ -112,61 +128,126 @@ def as_reversed_hex_array(value):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
def max_connections() -> int:
|
||||||
{
|
return IDF_MAX_CONNECTIONS if CORE.using_esp_idf else DEFAULT_MAX_CONNECTIONS
|
||||||
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(
|
def consume_connection_slots(
|
||||||
cv.Schema(
|
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(
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
CONF_DURATION, default="5min"
|
ESPBTAdvertiseTrigger
|
||||||
): cv.positive_time_period_seconds,
|
),
|
||||||
cv.Optional(
|
cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address),
|
||||||
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_SERVICE_DATA_ADVERTISE
|
||||||
cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation(
|
): automation.validate_automation(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address),
|
BLEServiceDataAdvertiseTrigger
|
||||||
}
|
),
|
||||||
),
|
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||||
cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation(
|
cv.Required(CONF_SERVICE_UUID): bt_uuid,
|
||||||
{
|
}
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
),
|
||||||
BLEServiceDataAdvertiseTrigger
|
cv.Optional(
|
||||||
),
|
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE
|
||||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
): automation.validate_automation(
|
||||||
cv.Required(CONF_SERVICE_UUID): bt_uuid,
|
{
|
||||||
}
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
),
|
BLEManufacturerDataAdvertiseTrigger
|
||||||
cv.Optional(
|
),
|
||||||
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE
|
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||||
): automation.validate_automation(
|
cv.Required(CONF_MANUFACTURER_ID): bt_uuid,
|
||||||
{
|
}
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
),
|
||||||
BLEManufacturerDataAdvertiseTrigger
|
cv.Optional(CONF_ON_SCAN_END): automation.validate_automation(
|
||||||
),
|
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)}
|
||||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
),
|
||||||
cv.Required(CONF_MANUFACTURER_ID): bt_uuid,
|
}
|
||||||
}
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
),
|
)
|
||||||
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(
|
ESP_BLE_DEVICE_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
@ -238,6 +319,9 @@ async def to_code(config):
|
|||||||
else:
|
else:
|
||||||
add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192)
|
add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192)
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9)
|
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
|
# CONFIG_BT_GATTC_NOTIF_REG_MAX controls the number of
|
||||||
# max notifications in 5.x, setting CONFIG_BT_ACL_CONNECTIONS
|
# max notifications in 5.x, setting CONFIG_BT_ACL_CONNECTIONS
|
||||||
# is enough in 4.x
|
# 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
|
<<: !include common.yaml
|
||||||
|
|
||||||
|
esp32_ble_tracker:
|
||||||
|
max_connections: 3
|
||||||
|
@ -1 +1,4 @@
|
|||||||
<<: !include common.yaml
|
<<: !include common.yaml
|
||||||
|
|
||||||
|
esp32_ble_tracker:
|
||||||
|
max_connections: 3
|
||||||
|
@ -1 +1,4 @@
|
|||||||
<<: !include common.yaml
|
<<: !include common.yaml
|
||||||
|
|
||||||
|
esp32_ble_tracker:
|
||||||
|
max_connections: 9
|
||||||
|
@ -1 +1,4 @@
|
|||||||
<<: !include common.yaml
|
<<: !include common.yaml
|
||||||
|
|
||||||
|
esp32_ble_tracker:
|
||||||
|
max_connections: 9
|
||||||
|
Loading…
x
Reference in New Issue
Block a user