esphome / issues

Issue Tracker for ESPHome
https://esphome.io/
290 stars 35 forks source link

ble_client removing stored services prevents using them to write data #3449

Open bdr99 opened 2 years ago

bdr99 commented 2 years ago

The problem

I'm trying to write to a BLE characteristic from a template button lambda. The config I'm using looks like this:

esp32_ble_tracker:

ble_client:
  - mac_address: XX:XX:XX:XX:XX:XX
    id: ble_device_client

button:
  - platform: template
    name: "Template Button"
    on_press:
      - lambda: |-
          esphome::ble_client::BLEClient* client = id(ble_device_client);

          auto service_uuid = 0xFFF0;
          auto char_uuid = 0xFFF1;

          unsigned char data[11] = {0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC};

          esphome::ble_client::BLECharacteristic* characteristic = client->get_characteristic(service_uuid, char_uuid);

          if (characteristic == nullptr) {
            ESP_LOGW("ble_client", "Characteristic not found!");
            return;
          }

          int status = esp_ble_gattc_write_char(
            client->gattc_if,
            client->conn_id,
            characteristic->handle,
            sizeof(data),
            data,
            ESP_GATT_WRITE_TYPE_NO_RSP,
            ESP_GATT_AUTH_REQ_NONE
          );

          if (status) {
            ESP_LOGW("ble_client", "Error sending write value to BLE gattc server, status=%d", status);
          }

When the ESP32 starts up, I can see the services and characteristics in the logs, including the one I am trying to write to. However, when I run this code, it outputs Characteristic not found!, indicating that the call to get_characteristic() returned null. After some debugging, I found that the services_ vector in the BLEClient was empty because it had been cleared by this code from ble_client.cpp:

  // Delete characteristics after clients have used them to save RAM.
  if (!all_established && this->all_nodes_established_()) {
    for (auto &svc : this->services_)
      delete svc;  // NOLINT(cppcoreguidelines-owning-memory)
    this->services_.clear();
  }

If I modify ble_client.cpp to remove this code, then I am able to successfully write to the characteristic. I understand that this is being done to conserve RAM, but doesn't this prevent using those services later? How can we write to a characteristic if the services are being deleted after they are discovered? Maybe I'm just missing something; I would appreciate any clarification.

Which version of ESPHome has the issue?

2022.6.2

What type of installation are you using?

Docker

Which version of Home Assistant has the issue?

No response

What platform are you using?

ESP32

Board

nodemcu

Component causing the issue

ble_client

Example YAML snippet

No response

Anything in the logs that might be useful for us?

No response

Additional information

No response

jhansche commented 1 year ago

How can we write to a characteristic if the services are being deleted after they are discovered?

Other components using ble_client do this by calling get_characteristic() during the ESP_GATTC_SEARCH_CMPL_EVT event handler, before transitioning to this->node_state = espbt::ClientState::ESTABLISHED (which is what triggers the flushing of seen services/characteristics). But that's harder to do when you're just using templates and lambdas for your component, rather than an actual component class implementation.

But you might be able to "stash" the BLECharacteristic in a global variable from your ble_client.on_connect automation.

The Automation will both set the ESTABLISHED state and trigger the automation lambda:

    if (event == ESP_GATTC_SEARCH_CMPL_EVT) {
      this->node_state = espbt::ClientState::ESTABLISHED;
      this->trigger();
    }

however, the service cleanup won't happen until after all children (including the BLEClientConnectTrigger) are given a chance to run their gattc handlers, so the on_connect automation will trigger before that cleanup occurs.