mirror of
https://github.com/esphome/esphome.git
synced 2026-08-23 06:36:23 +00:00
Deliver completions through a GattClientListener interface
This commit is contained in:
@@ -8,9 +8,9 @@
|
||||
// A consumer - a streaming consumer that forwards the raw database (the hub
|
||||
// BluetoothConnection wrapper) or a direct consumer owning a dedicated
|
||||
// backend and resolving handles by UUID - drives it and receives
|
||||
// completions through a GattEventSink — a pointer-sized-entry function table
|
||||
// rather than a concrete consumer type, because one build can hold several
|
||||
// consumer types while the backend stays a single non-virtual class. All sink
|
||||
// completions through the GattClientListener interface (one build can hold
|
||||
// several consumer types while the backend stays a single non-virtual
|
||||
// class). All listener
|
||||
// calls are delivered on the ESPHome main loop; borrowed data pointers are
|
||||
// valid only for the duration of the call.
|
||||
//
|
||||
@@ -82,90 +82,31 @@ struct GattServiceTable {
|
||||
uint16_t descriptor_count{0};
|
||||
};
|
||||
|
||||
// The event sink the backend calls directly, asserted where each consumer is
|
||||
// defined: on_connection_state carries the negotiated MTU and an HCI
|
||||
// status/disconnect reason. The requirements check call validity, not exact
|
||||
// parameter types; keep sink parameters at the documented widths (uint16_t
|
||||
// handles and lengths).
|
||||
template<typename S>
|
||||
concept GattClientEventSinkContract = requires(S sink, const uint8_t *data) {
|
||||
{ sink.on_connection_state(true, uint16_t{}, int{}) } -> std::same_as<void>;
|
||||
{ sink.on_service_discovery_done(int{}) } -> std::same_as<void>;
|
||||
{ sink.on_read_result(uint16_t{}, data, uint16_t{}, int{}) } -> std::same_as<void>;
|
||||
{ sink.on_write_result(uint16_t{}, int{}) } -> std::same_as<void>;
|
||||
{ sink.on_notify_state(uint16_t{}, true, int{}) } -> std::same_as<void>;
|
||||
{ sink.on_notify_data(uint16_t{}, data, uint16_t{}) } -> std::same_as<void>;
|
||||
{ sink.on_pairing_result(int{}) } -> std::same_as<void>;
|
||||
};
|
||||
|
||||
/// One trampoline per event, shared by every instance of a consumer type.
|
||||
struct GattEventVTable {
|
||||
void (*connection_state)(void *, bool, uint16_t, int);
|
||||
void (*service_discovery_done)(void *, int);
|
||||
void (*read_result)(void *, uint16_t, const uint8_t *, uint16_t, int);
|
||||
void (*write_result)(void *, uint16_t, int);
|
||||
void (*notify_state)(void *, uint16_t, bool, int);
|
||||
void (*notify_data)(void *, uint16_t, const uint8_t *, uint16_t);
|
||||
void (*pairing_result)(void *, int);
|
||||
};
|
||||
|
||||
// The per-consumer-type table lives in flash (constexpr), so a sink costs
|
||||
// two pointers of RAM regardless of how many events the surface carries.
|
||||
template<typename T>
|
||||
inline constexpr GattEventVTable GATT_EVENT_VTABLE{
|
||||
[](void *p, bool connected, uint16_t mtu, int error) {
|
||||
static_cast<T *>(p)->on_connection_state(connected, mtu, error);
|
||||
},
|
||||
[](void *p, int error) { static_cast<T *>(p)->on_service_discovery_done(error); },
|
||||
[](void *p, uint16_t handle, const uint8_t *data, uint16_t len, int error) {
|
||||
static_cast<T *>(p)->on_read_result(handle, data, len, error);
|
||||
},
|
||||
[](void *p, uint16_t handle, int error) { static_cast<T *>(p)->on_write_result(handle, error); },
|
||||
[](void *p, uint16_t handle, bool enabled, int error) {
|
||||
static_cast<T *>(p)->on_notify_state(handle, enabled, error);
|
||||
},
|
||||
[](void *p, uint16_t handle, const uint8_t *data, uint16_t len) {
|
||||
static_cast<T *>(p)->on_notify_data(handle, data, len);
|
||||
},
|
||||
[](void *p, int status) { static_cast<T *>(p)->on_pairing_result(status); },
|
||||
};
|
||||
|
||||
/// Type-erased consumer handle a backend delivers events through: an
|
||||
/// instance pointer plus the consumer type's trampoline table. Built with
|
||||
/// make_gatt_sink() from any type satisfying GattClientEventSinkContract;
|
||||
/// call sites read the same as a direct listener call. No virtuals, no heap —
|
||||
/// the cost of supporting several consumer types in one build is one
|
||||
/// indirect call per event. Codegen wires the sink before setup(), so
|
||||
/// The event surface a backend delivers completions through. Genuine runtime
|
||||
/// polymorphism lives here - one build can hold several consumer types (the
|
||||
/// proxy's connection wrapper, dedicated-backend components) against the one
|
||||
/// non-virtual backend class - so this is a plain virtual interface: every
|
||||
/// method defaults to a no-op, consumers override what they consume (override
|
||||
/// makes a misspelled name a compile error), and adding an event touches no
|
||||
/// existing consumer. No destructor: components are never destroyed, and
|
||||
/// nothing deletes through this base.
|
||||
/// on_connection_state carries the negotiated MTU and an HCI
|
||||
/// status/disconnect reason. Codegen wires the listener before setup(), so
|
||||
/// backends may call without a null check.
|
||||
struct GattEventSink {
|
||||
void *instance{nullptr};
|
||||
const GattEventVTable *vtable{nullptr};
|
||||
|
||||
void on_connection_state(bool connected, uint16_t mtu, int error) const {
|
||||
this->vtable->connection_state(this->instance, connected, mtu, error);
|
||||
}
|
||||
void on_service_discovery_done(int error) const { this->vtable->service_discovery_done(this->instance, error); }
|
||||
void on_read_result(uint16_t handle, const uint8_t *data, uint16_t len, int error) const {
|
||||
this->vtable->read_result(this->instance, handle, data, len, error);
|
||||
}
|
||||
void on_write_result(uint16_t handle, int error) const { this->vtable->write_result(this->instance, handle, error); }
|
||||
void on_notify_state(uint16_t handle, bool enabled, int error) const {
|
||||
this->vtable->notify_state(this->instance, handle, enabled, error);
|
||||
}
|
||||
void on_notify_data(uint16_t handle, const uint8_t *data, uint16_t len) const {
|
||||
this->vtable->notify_data(this->instance, handle, data, len);
|
||||
}
|
||||
void on_pairing_result(int status) const { this->vtable->pairing_result(this->instance, status); }
|
||||
class GattClientListener {
|
||||
public:
|
||||
virtual void on_connection_state(bool connected, uint16_t mtu, int error) {}
|
||||
virtual void on_service_discovery_done(int error) {}
|
||||
virtual void on_read_result(uint16_t handle, const uint8_t *data, uint16_t len, int error) {}
|
||||
virtual void on_write_result(uint16_t handle, int error) {}
|
||||
virtual void on_notify_state(uint16_t handle, bool enabled, int error) {}
|
||||
virtual void on_notify_data(uint16_t handle, const uint8_t *data, uint16_t len) {}
|
||||
virtual void on_pairing_result(int status) {}
|
||||
};
|
||||
|
||||
template<typename T> GattEventSink make_gatt_sink(T *consumer) {
|
||||
static_assert(GattClientEventSinkContract<T>, "the consumer is missing part of the event-sink surface");
|
||||
return {consumer, &GATT_EVENT_VTABLE<T>};
|
||||
}
|
||||
|
||||
// The BLEGattConnection op surface, asserted where the alias binds
|
||||
// (bluetooth_connection_gatt_backend.h). Operations return 0 when accepted (completion arrives
|
||||
// through the sink) or a synchronous error (busy, not connected, stack
|
||||
// through the listener) or a synchronous error (busy, not connected, stack
|
||||
// rejection); one operation may be outstanding at a time. Semantics beyond
|
||||
// the signatures:
|
||||
// - connect: addr_type is a BLE_ADDR_TYPE_* constant (ble_device.h).
|
||||
@@ -184,8 +125,8 @@ template<typename T> GattEventSink make_gatt_sink(T *consumer) {
|
||||
// on_notify_state, characteristic writes with response and descriptor
|
||||
// writes in on_write_result.
|
||||
template<typename T>
|
||||
concept BLEGattConnectionContract = requires(T conn, GattEventSink sink, const uint8_t *data) {
|
||||
conn.set_sink(sink);
|
||||
concept BLEGattConnectionContract = requires(T conn, GattClientListener *listener, const uint8_t *data) {
|
||||
conn.set_listener(listener);
|
||||
{ conn.connect(uint64_t{}, uint8_t{}) } -> std::same_as<int>;
|
||||
{ conn.disconnect() } -> std::same_as<int>;
|
||||
{ conn.discover_services() } -> std::same_as<int>;
|
||||
|
||||
@@ -436,7 +436,7 @@ void BluedroidGattClient::set_disconnecting_() {
|
||||
}
|
||||
|
||||
void BluedroidGattClient::report_connection_state_(bool connected, int error) {
|
||||
this->sink_.on_connection_state(connected, this->mtu_, error);
|
||||
this->listener_->on_connection_state(connected, this->mtu_, error);
|
||||
}
|
||||
|
||||
esp_err_t BluedroidGattClient::update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency,
|
||||
@@ -478,11 +478,11 @@ void BluedroidGattClient::handle_search_cmpl_() {
|
||||
// clients cache the streamed result permanently.
|
||||
auto status = primary_status != ESP_GATT_OK ? primary_status : secondary_status;
|
||||
this->log_gattc_warning_("esp_ble_gattc_get_attr_count", status);
|
||||
this->sink_.on_service_discovery_done(status);
|
||||
this->listener_->on_service_discovery_done(status);
|
||||
return;
|
||||
}
|
||||
this->service_total_ = primary + secondary;
|
||||
this->sink_.on_service_discovery_done(0);
|
||||
this->listener_->on_service_discovery_done(0);
|
||||
}
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
@@ -765,32 +765,34 @@ bool BluedroidGattClient::handle_gattc_event_(esp_gattc_cb_event_t event, esp_ga
|
||||
if (this->conn_id_ != param->read.conn_id)
|
||||
return false;
|
||||
bool ok = param->read.status == ESP_GATT_OK;
|
||||
this->sink_.on_read_result(param->read.handle, ok ? param->read.value : nullptr, ok ? param->read.value_len : 0,
|
||||
ok ? 0 : param->read.status);
|
||||
this->listener_->on_read_result(param->read.handle, ok ? param->read.value : nullptr,
|
||||
ok ? param->read.value_len : 0, ok ? 0 : param->read.status);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
if (this->conn_id_ != param->write.conn_id)
|
||||
return false;
|
||||
this->sink_.on_write_result(param->write.handle, param->write.status == ESP_GATT_OK ? 0 : param->write.status);
|
||||
this->listener_->on_write_result(param->write.handle,
|
||||
param->write.status == ESP_GATT_OK ? 0 : param->write.status);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->sink_.on_notify_state(param->reg_for_notify.handle, true,
|
||||
param->reg_for_notify.status == ESP_GATT_OK ? 0 : param->reg_for_notify.status);
|
||||
this->listener_->on_notify_state(param->reg_for_notify.handle, true,
|
||||
param->reg_for_notify.status == ESP_GATT_OK ? 0 : param->reg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||
this->sink_.on_notify_state(param->unreg_for_notify.handle, false,
|
||||
param->unreg_for_notify.status == ESP_GATT_OK ? 0 : param->unreg_for_notify.status);
|
||||
this->listener_->on_notify_state(
|
||||
param->unreg_for_notify.handle, false,
|
||||
param->unreg_for_notify.status == ESP_GATT_OK ? 0 : param->unreg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (this->conn_id_ != param->notify.conn_id)
|
||||
return false;
|
||||
ESP_LOGV(TAG, "[%d] NOTIFY_EVT handle=0x%2X", this->connection_index_, param->notify.handle);
|
||||
this->sink_.on_notify_data(param->notify.handle, param->notify.value, param->notify.value_len);
|
||||
this->listener_->on_notify_data(param->notify.handle, param->notify.value, param->notify.value_len);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -811,8 +813,8 @@ void BluedroidGattClient::handle_gap_event_(esp_gap_ble_cb_event_t event, esp_bl
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
|
||||
if (!this->check_addr_(param->ble_security.auth_cmpl.bd_addr))
|
||||
break;
|
||||
this->sink_.on_pairing_result(param->ble_security.auth_cmpl.success ? 0
|
||||
: param->ble_security.auth_cmpl.fail_reason);
|
||||
this->listener_->on_pairing_result(
|
||||
param->ble_security.auth_cmpl.success ? 0 : param->ble_security.auth_cmpl.fail_reason);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -55,7 +55,7 @@ class BluedroidGattClient final : public Component {
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_BLUETOOTH; }
|
||||
|
||||
// Wired by codegen before setup and invariant for the device lifetime.
|
||||
void set_sink(ble_device_base::GattEventSink sink) { this->sink_ = sink; }
|
||||
void set_listener(ble_device_base::GattClientListener *listener) { this->listener_ = listener; }
|
||||
esp32_ble_tracker::ESPBTClient *tracker_client() { return &this->shim_; }
|
||||
|
||||
// ---- ble_device_base::BLEGattConnection contract ----
|
||||
@@ -123,7 +123,7 @@ class BluedroidGattClient final : public Component {
|
||||
|
||||
// Group 1: pointers / composed objects
|
||||
BluedroidTrackerShim shim_{this};
|
||||
ble_device_base::GattEventSink sink_;
|
||||
ble_device_base::GattClientListener *listener_{nullptr};
|
||||
#ifdef USE_BLE_GATT_SERVICE_TABLE
|
||||
// One exact-size block carved into the table's three arrays; owned here,
|
||||
// freed by release_services(). Null when no table is materialized. The
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace esphome::bluetooth_connection {
|
||||
|
||||
class StubGattBackend {
|
||||
public:
|
||||
void set_sink(ble_device_base::GattEventSink sink) {}
|
||||
void set_listener(ble_device_base::GattClientListener *listener) {}
|
||||
int connect(uint64_t address, uint8_t addr_type) { return ble_device_base::GATT_ERR_NOT_CONNECTED; }
|
||||
int disconnect() { return ble_device_base::GATT_ERR_NOT_CONNECTED; }
|
||||
int discover_services() { return ble_device_base::GATT_ERR_NOT_CONNECTED; }
|
||||
|
||||
@@ -96,7 +96,7 @@ void BluetoothConnection::reset_connection_(conn_err_t reason) {
|
||||
this->proxy_->reset_connection_slot_(this, reason);
|
||||
}
|
||||
|
||||
// ---- backend event sink ----
|
||||
// ---- backend event listener ----
|
||||
|
||||
void BluetoothConnection::on_connection_state(bool connected, uint16_t mtu, int error) {
|
||||
if (connected && this->address_ == 0) {
|
||||
|
||||
@@ -24,12 +24,12 @@ namespace esphome::bluetooth_connection {
|
||||
using ClientState = ble_device_base::ClientState;
|
||||
using ConnectionType = ble_device_base::ConnectionType;
|
||||
|
||||
class BluetoothConnection final {
|
||||
class BluetoothConnection final : public ble_device_base::GattClientListener {
|
||||
public:
|
||||
/// Wire the platform backend. Called from codegen before setup.
|
||||
void set_backend(ble_device_base::BLEGattConnection *backend) {
|
||||
this->backend_ = backend;
|
||||
backend->set_sink(ble_device_base::make_gatt_sink(this));
|
||||
backend->set_listener(this);
|
||||
}
|
||||
|
||||
// ---- proxy dispatch surface ----
|
||||
@@ -88,14 +88,14 @@ class BluetoothConnection final {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- backend event sink (called directly by the backend, main loop) ----
|
||||
void on_connection_state(bool connected, uint16_t mtu, int error);
|
||||
void on_service_discovery_done(int error);
|
||||
void on_read_result(uint16_t handle, const uint8_t *data, uint16_t len, int error);
|
||||
void on_write_result(uint16_t handle, int error);
|
||||
void on_notify_state(uint16_t handle, bool enabled, int error);
|
||||
void on_notify_data(uint16_t handle, const uint8_t *data, uint16_t len);
|
||||
void on_pairing_result(int status);
|
||||
// ---- backend event listener (called directly by the backend, main loop) ----
|
||||
void on_connection_state(bool connected, uint16_t mtu, int error) override;
|
||||
void on_service_discovery_done(int error) override;
|
||||
void on_read_result(uint16_t handle, const uint8_t *data, uint16_t len, int error) override;
|
||||
void on_write_result(uint16_t handle, int error) override;
|
||||
void on_notify_state(uint16_t handle, bool enabled, int error) override;
|
||||
void on_notify_data(uint16_t handle, const uint8_t *data, uint16_t len) override;
|
||||
void on_pairing_result(int status) override;
|
||||
|
||||
protected:
|
||||
friend class bluetooth_proxy::BluetoothProxy;
|
||||
@@ -146,9 +146,6 @@ class BluetoothConnection final {
|
||||
bool services_discovered_{false};
|
||||
};
|
||||
|
||||
static_assert(ble_device_base::GattClientEventSinkContract<BluetoothConnection>,
|
||||
"The hub wrapper is missing part of the event-sink surface (ble_gatt_client.h)");
|
||||
|
||||
} // namespace esphome::bluetooth_connection
|
||||
|
||||
#endif // USE_BLE_GATT_CLIENT
|
||||
|
||||
@@ -383,7 +383,7 @@ void RP2GattClient::loop() {
|
||||
RP2GattNotifyEvent *notify;
|
||||
while ((notify = this->notify_queue_.pop()) != nullptr) {
|
||||
if (this->notify_subscribed_(notify->handle)) {
|
||||
this->sink_.on_notify_data(notify->handle, notify->data, notify->len);
|
||||
this->listener_->on_notify_data(notify->handle, notify->data, notify->len);
|
||||
}
|
||||
this->notify_pool_.release(notify);
|
||||
}
|
||||
@@ -446,7 +446,7 @@ void RP2GattClient::loop() {
|
||||
}
|
||||
if (timed_out) {
|
||||
ESP_LOGW(TAG, "Deferred write timeout, handle=0x%04x", this->op_handle_);
|
||||
this->sink_.on_write_result(this->op_handle_, GATT_CLIENT_BUSY);
|
||||
this->listener_->on_write_result(this->op_handle_, GATT_CLIENT_BUSY);
|
||||
}
|
||||
} else if (this->state_ == EngineState::IDLE || (this->state_ == EngineState::READY && !this->op_in_flight_() &&
|
||||
this->event_queue_.empty() && this->notify_queue_.empty())) {
|
||||
@@ -470,7 +470,7 @@ void RP2GattClient::handle_event_(const RP2GattEvent &event) {
|
||||
this->state_ = EngineState::READY;
|
||||
// Scanning resumes and runs alongside the established connection.
|
||||
this->release_scan_inhibit_();
|
||||
this->sink_.on_connection_state(true, this->mtu_, 0);
|
||||
this->listener_->on_connection_state(true, this->mtu_, 0);
|
||||
}
|
||||
break;
|
||||
case RP2GattEvent::QUERY_COMPLETE:
|
||||
@@ -480,7 +480,7 @@ void RP2GattClient::handle_event_(const RP2GattEvent &event) {
|
||||
this->finish_write_no_rsp_(event.status);
|
||||
break;
|
||||
case RP2GattEvent::PAIRING_RESULT:
|
||||
this->sink_.on_pairing_result(event.status);
|
||||
this->listener_->on_pairing_result(event.status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -506,7 +506,7 @@ void RP2GattClient::finish_write_no_rsp_(uint8_t status) {
|
||||
return;
|
||||
}
|
||||
this->op_type_ = OpType::NONE;
|
||||
this->sink_.on_write_result(this->op_handle_, status);
|
||||
this->listener_->on_write_result(this->op_handle_, status);
|
||||
}
|
||||
|
||||
void RP2GattClient::handle_connected_(uint8_t status, uint16_t con_handle) {
|
||||
@@ -566,7 +566,7 @@ void RP2GattClient::fail_connection_(uint8_t reason) {
|
||||
this->cleanup_link_state_();
|
||||
this->release_scan_inhibit_();
|
||||
this->state_ = EngineState::IDLE;
|
||||
this->sink_.on_connection_state(false, 0, reason);
|
||||
this->listener_->on_connection_state(false, 0, reason);
|
||||
}
|
||||
|
||||
void RP2GattClient::cleanup_link_state_() {
|
||||
@@ -619,11 +619,12 @@ void RP2GattClient::handle_query_complete_(uint8_t att_status) {
|
||||
this->op_len_ > 0) {
|
||||
att_status = 0;
|
||||
}
|
||||
this->sink_.on_read_result(this->op_handle_, this->op_buffer_, att_status == 0 ? this->op_len_ : 0, att_status);
|
||||
this->listener_->on_read_result(this->op_handle_, this->op_buffer_, att_status == 0 ? this->op_len_ : 0,
|
||||
att_status);
|
||||
break;
|
||||
case OpType::WRITE_CHAR:
|
||||
case OpType::WRITE_DESC:
|
||||
this->sink_.on_write_result(this->op_handle_, att_status);
|
||||
this->listener_->on_write_result(this->op_handle_, att_status);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -780,7 +781,7 @@ void RP2GattClient::finish_discovery_(int error) {
|
||||
if (error != 0) {
|
||||
this->release_services();
|
||||
}
|
||||
this->sink_.on_service_discovery_done(error);
|
||||
this->listener_->on_service_discovery_done(error);
|
||||
}
|
||||
|
||||
ble_device_base::GattServiceTable RP2GattClient::get_service_table() {
|
||||
@@ -973,7 +974,7 @@ int RP2GattClient::write_characteristic(uint16_t handle, const uint8_t *data, ui
|
||||
}
|
||||
}
|
||||
if (status == 0) {
|
||||
this->sink_.on_write_result(handle, 0);
|
||||
this->listener_->on_write_result(handle, 0);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@@ -1074,7 +1075,7 @@ int RP2GattClient::notify_characteristic(uint16_t handle, bool enable) {
|
||||
}
|
||||
}
|
||||
}
|
||||
this->sink_.on_notify_state(handle, enable, 0);
|
||||
this->listener_->on_notify_state(handle, enable, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ class RP2GattClient final : public Component, public Parented<rp2040_ble::RP2040
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void set_sink(ble_device_base::GattEventSink sink) { this->sink_ = sink; }
|
||||
void set_listener(ble_device_base::GattClientListener *listener) { this->listener_ = listener; }
|
||||
|
||||
// ---- ble_device_base::BLEGattConnection contract ----
|
||||
int connect(uint64_t address, uint8_t addr_type);
|
||||
@@ -153,7 +153,7 @@ class RP2GattClient final : public Component, public Parented<rp2040_ble::RP2040
|
||||
}
|
||||
|
||||
// Group 1: containers / large storage
|
||||
ble_device_base::GattEventSink sink_;
|
||||
ble_device_base::GattClientListener *listener_{nullptr};
|
||||
ServiceArena *arena_{nullptr};
|
||||
esphome::LockFreeQueue<RP2GattEvent, RP2_GATT_EVENT_QUEUE_SIZE> event_queue_;
|
||||
esphome::EventPool<RP2GattEvent, RP2_GATT_EVENT_QUEUE_SIZE - 1> event_pool_;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// configured; this TU pins it on the host so the header cannot rot unseen.
|
||||
// The contract is a concept (BLEGattConnection is a per-platform alias), so
|
||||
// the minimal backend here proves the concept stays satisfiable and routes
|
||||
// events through the type-erased GattEventSink the way a real backend does.
|
||||
// events through the GattClientListener interface the way a real backend does.
|
||||
#define USE_BLE_GATT_CLIENT
|
||||
|
||||
#include "esphome/components/ble_device_base/ble_gatt_client.h"
|
||||
@@ -11,37 +11,34 @@
|
||||
|
||||
namespace esphome::ble_device_base::testing {
|
||||
|
||||
struct RecordingSink {
|
||||
void on_connection_state(bool connected, uint16_t mtu, int error) { this->connected_ = connected; }
|
||||
void on_service_discovery_done(int error) { this->discovery_error_ = error; }
|
||||
void on_read_result(uint16_t handle, const uint8_t *data, uint16_t len, int error) {}
|
||||
void on_write_result(uint16_t handle, int error) { this->write_handle_ = handle; }
|
||||
void on_notify_state(uint16_t handle, bool enabled, int error) {}
|
||||
void on_notify_data(uint16_t handle, const uint8_t *data, uint16_t len) {}
|
||||
void on_pairing_result(int status) {}
|
||||
// Overrides only what it records; the interface's defaults cover the rest.
|
||||
class RecordingListener : public GattClientListener {
|
||||
public:
|
||||
void on_connection_state(bool connected, uint16_t mtu, int error) override { this->connected_ = connected; }
|
||||
void on_service_discovery_done(int error) override { this->discovery_error_ = error; }
|
||||
void on_write_result(uint16_t handle, int error) override { this->write_handle_ = handle; }
|
||||
|
||||
bool connected_{false};
|
||||
int discovery_error_{0};
|
||||
uint16_t write_handle_{0};
|
||||
};
|
||||
|
||||
static_assert(GattClientEventSinkContract<RecordingSink>, "the recording sink must cover the full event-sink surface");
|
||||
|
||||
class MinimalConnection {
|
||||
public:
|
||||
void set_sink(GattEventSink sink) { this->sink_ = sink; }
|
||||
void set_listener(GattClientListener *listener) { this->listener_ = listener; }
|
||||
|
||||
int connect(uint64_t address, uint8_t addr_type) {
|
||||
this->sink_.on_connection_state(true, 517, 0);
|
||||
this->listener_->on_connection_state(true, 517, 0);
|
||||
return 0;
|
||||
}
|
||||
int disconnect() { return 0; }
|
||||
int discover_services() {
|
||||
this->sink_.on_service_discovery_done(0);
|
||||
this->listener_->on_service_discovery_done(0);
|
||||
return 0;
|
||||
}
|
||||
int read_characteristic(uint16_t handle) { return GATT_ERR_NOT_CONNECTED; }
|
||||
int write_characteristic(uint16_t handle, const uint8_t *data, uint16_t len, bool response) {
|
||||
this->sink_.on_write_result(handle, 0);
|
||||
this->listener_->on_write_result(handle, 0);
|
||||
return 0;
|
||||
}
|
||||
int read_descriptor(uint16_t handle) { return 0; }
|
||||
@@ -58,7 +55,7 @@ class MinimalConnection {
|
||||
void set_connection_type(ConnectionType ct) {}
|
||||
|
||||
protected:
|
||||
GattEventSink sink_;
|
||||
GattClientListener *listener_{nullptr};
|
||||
};
|
||||
|
||||
static_assert(BLEGattConnectionContract<MinimalConnection>,
|
||||
@@ -66,8 +63,8 @@ static_assert(BLEGattConnectionContract<MinimalConnection>,
|
||||
|
||||
TEST(BleGattClientContract, MinimalImplementerCompilesAndRoutesEvents) {
|
||||
MinimalConnection connection;
|
||||
RecordingSink listener;
|
||||
connection.set_sink(make_gatt_sink(&listener));
|
||||
RecordingListener listener;
|
||||
connection.set_listener(&listener);
|
||||
EXPECT_EQ(connection.connect(0xAABBCCDDEEFFULL, 0), 0);
|
||||
EXPECT_TRUE(listener.connected_);
|
||||
EXPECT_EQ(connection.discover_services(), 0);
|
||||
|
||||
Reference in New Issue
Block a user