mirror of
https://github.com/esphome/esphome.git
synced 2026-10-02 01:10:22 +00:00
Address review: empty-table failure semantics, async abort guard, StaticVector nodes
- A zero-service table on a client with nodes is treated as a failed discovery (warn, release, charge the backoff, disconnect): a real GATT peer always exposes at least GAP/GATT, so an empty table means the materialization failed and the connection must retry instead of sitting inert behind a successful on_connect - The fan-out abort guard also tests cancel_requested_, covering the normal async teardown a node starts from on_connected (the state-only check caught just the synchronous refusal) - nodes_ becomes a StaticVector sized by ESPHOME_BLE_CLIENT_MAX_NODES: the client requests a baseline slot, each neutral write action (which registers itself in its constructor) requests one, and the define rides defines.h for analysis - no realloc machinery on the rp2 target - The Bluedroid empty-table return logs its quiet cases; the service_table=False stub notes the misconfiguration it implies
This commit is contained in:
@@ -350,6 +350,9 @@ async def ble_connect_to_code(config, action_id, template_arg, args):
|
||||
)
|
||||
async def ble_write_to_code(config, action_id, template_arg, args):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
if not CORE.is_esp32:
|
||||
# The neutral action registers itself as a node in its constructor.
|
||||
_request_node_slot()
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
value = config[CONF_VALUE]
|
||||
@@ -448,7 +451,13 @@ async def _to_code_esp32(config: ConfigType) -> cg.MockObj:
|
||||
return var
|
||||
|
||||
|
||||
# Sizes the neutral client's node storage; the client itself requests a
|
||||
# baseline slot so the define exists on every build that compiles the engine.
|
||||
_request_node_slot = cg.slot_counter("ESPHOME_BLE_CLIENT_MAX_NODES")
|
||||
|
||||
|
||||
async def _to_code_gatt(config: ConfigType) -> cg.MockObj:
|
||||
_request_node_slot()
|
||||
backend = await bluetooth_connection.new_gatt_backend(config)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -160,13 +160,19 @@ void BLEClient::on_service_discovery_done(int error) {
|
||||
// would pay the build/free cycle on every (re)connect for nothing.
|
||||
auto table = this->backend_->get_service_table();
|
||||
if (table.service_count == 0) {
|
||||
// A service-less device or a failed materialization: either way the
|
||||
// nodes cannot resolve, so say so instead of sitting inert.
|
||||
ESP_LOGW(TAG, "[%s] Service table is empty; nodes will not resolve", this->address_str_);
|
||||
// A failed materialization is indistinguishable from a service-less
|
||||
// peer, and a real GATT peer always exposes at least GAP/GATT: treat
|
||||
// it as a discovery failure so the connection retries instead of
|
||||
// sitting inert behind a successful-looking on_connect.
|
||||
ESP_LOGW(TAG, "[%s] Service table is empty; treating as failed discovery", this->address_str_);
|
||||
this->backend_->release_services();
|
||||
this->register_failure_();
|
||||
this->disconnect();
|
||||
return;
|
||||
}
|
||||
for (auto *node : this->nodes_) {
|
||||
node->on_connected(table);
|
||||
if (this->state_ != State::CONNECTED) {
|
||||
if (this->state_ != State::CONNECTED || this->cancel_requested_) {
|
||||
// A node tore the link down mid-fan-out: on_disconnect fires with no
|
||||
// preceding on_connect, so leave a trace of why.
|
||||
ESP_LOGW(TAG, "[%s] A node aborted the connection during setup", this->address_str_);
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::ble_client {
|
||||
|
||||
@@ -133,7 +132,8 @@ class BLEClient : public Component,
|
||||
|
||||
// Group 1: pointers / containers
|
||||
ble_device_base::BLEGattConnection *backend_{nullptr};
|
||||
std::vector<BLEClientNode *> nodes_; // filled during setup, never after
|
||||
// Codegen-sized (ESPHOME_BLE_CLIENT_MAX_NODES); filled during setup.
|
||||
StaticVector<BLEClientNode *, ESPHOME_BLE_CLIENT_MAX_NODES> nodes_;
|
||||
|
||||
// Group 2: 8-byte types
|
||||
uint64_t address_{0};
|
||||
|
||||
@@ -324,6 +324,10 @@ void BluedroidGattClient::release_services() {
|
||||
ble_device_base::GattServiceTable BluedroidGattClient::get_service_table() {
|
||||
if (this->table_storage_ == nullptr &&
|
||||
(this->services_released_ || this->service_total_ == 0 || !this->build_service_table_())) {
|
||||
// Released / no services / failed build all collapse to empty; the
|
||||
// build failures warned above, log the quiet two.
|
||||
ESP_LOGD(TAG, "[%d] No service table (released=%d, services=%u)", this->connection_index_, this->services_released_,
|
||||
this->service_total_);
|
||||
return {};
|
||||
}
|
||||
return this->table_view_();
|
||||
|
||||
@@ -80,6 +80,8 @@ class BluedroidGattClient final : public esp32_ble_tracker::ESPBTClient, public
|
||||
#ifdef USE_BLE_GATT_SERVICE_TABLE
|
||||
ble_device_base::GattServiceTable get_service_table();
|
||||
#else
|
||||
// A direct consumer reaching this stub misconfigured its codegen
|
||||
// (service_table=False): the empty table reads as a service-less peer.
|
||||
ble_device_base::GattServiceTable get_service_table() { return {}; }
|
||||
#endif
|
||||
void release_services();
|
||||
|
||||
@@ -474,6 +474,7 @@
|
||||
#define ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT 1
|
||||
#define USE_BLE_GATT_CLIENT
|
||||
#define ESPHOME_BLE_GATT_CLIENT_COUNT 1
|
||||
#define ESPHOME_BLE_CLIENT_MAX_NODES 1
|
||||
#define USE_RP2040_VARIANT_RP2040
|
||||
#define USE_SPI
|
||||
#ifndef USE_ETHERNET
|
||||
|
||||
Reference in New Issue
Block a user