[esp32_ble_server] Send advertisements with data

For some weeks I’m playing around with BLE and got it working sort of. The only thing I dislike is that it needs to connect to the ESPHome BLE server in order to get updates. In the process added some PRs to fix/improve the ESPHome documentation.

The disadvantage is that mostly only one BLE client can be connected. At least that is my experience. Might be because

I would like to have ESPHome advertise the values in the advertisement packets like Xiaomi temperature sensors do. I thought if I did ble_server.characteristic.notify it would automatically advertise this, but it looks like that is not the case.

Can it be done? Does it need custom lambda code?

This is my current code.

Server (sends a random int every second):

esp32_ble_server:
  id: ble_server
  services:
    - uuid: 0f502a49-9c9b-4788-b86e-5248e50d14b3
      advertise: true
      characteristics:
        - uuid: cf7e7a4b-4f74-4cbe-9d41-bfa40f846c5a
          id: ble_random_test_int
          description: "BLE random test int"
          broadcast: true
          read: true
          notify: true
          value:
            type: int32_t
            data: 1

interval:
  - interval: 1sec
    then:
      - ble_server.characteristic.set_value:
          id: ble_test
          value: !lambda
            float my_float_value = rand() % 2000;
            std::string my_string = str_sprintf("%.0f", my_float_value);
            std::vector<unsigned char> byte_vector(my_string.begin(), my_string.end());
            return byte_vector;

      - ble_server.characteristic.notify:
          id: ble_test

Client (logs advertisements):

esp32_ble_tracker:
  scan_parameters:
    continuous: false

  on_ble_advertise:
    - mac_address: XX:XX:XX:XX:XX:XX
      then:
        - lambda: |-
            ESP_LOGD("ble_adv", "New BLE device");
            ESP_LOGD("ble_adv", "  address: %s", x.address_str().c_str());
            ESP_LOGD("ble_adv", "  name: %s", x.get_name().c_str());

            ESP_LOGD("ble_adv", "  Advertised service UUIDs:");
            for (auto uuid : x.get_service_uuids()) {
                ESP_LOGD("ble_adv", "    - %s", uuid.to_string().c_str());
            }

            ESP_LOGD("ble_adv", "  Advertised service data:");
            for (auto data : x.get_service_datas()) {
                ESP_LOGD("ble_adv", "    - %s: (length %i)", data.uuid.to_string().c_str(), data.data.size());
                ESP_LOGD("ble_adv", "    - %s:", format_hex_pretty(data.data).c_str());
            }

            ESP_LOGD("ble_adv", "  Advertised manufacturer data:");

            for (auto data : x.get_manufacturer_datas()) {
                ESP_LOGD("ble_adv", "    - %s: (length %i)", data.uuid.to_string().c_str(), data.data.size());
                ESP_LOGD("ble_adv", "    - %s:", format_hex_pretty(data.data).c_str());
            }

  on_ble_service_data_advertise:
    - mac_address: XX:XX:XX:XX:XX:XX
      service_uuid: 0f502a49-9c9b-4788-b86e-5248e50d14b3
      then:
        - lambda: |-
            ESP_LOGD("ble_service_data_advertise", "    - %s", x);

The only thing I’m getting is:

[17:08:14][D][ble_adv:068]: New BLE device
[17:08:14][D][ble_adv:069]:   address: XX:XX:XX:XX:XX:XX
[17:08:14][D][ble_adv:070]:   name: esp32-ble-server
[17:08:14][D][ble_adv:072]:   Advertised service UUIDs:
[17:08:14][D][ble_adv:074]:     - 0F502A49-9C9B-4788-B86E-5248E50D14B3
[17:08:14][D][ble_adv:077]:   Advertised service data:
[17:08:14][D][ble_adv:083]:   Advertised manufacturer data:

Without any Advertised service data… Also tried with using capitals for service_uuid: 0f502a49-9c9b-4788-b86e-5248e50d14b3

Perhaps try with manufacturer’s data, that should be simpler. I don’t quite remember many details, since I did it a long time ago but I used manufacturer’s data to send time+distance from my treadmill to Home Assistant, and it worked well.

I can’t give you ESPhome code for sending end, as I used Arduino for that. But the code is very simple: create a buffer with made-up manufacturer’s ID + any data you want.

Arduino
char buff[6] = {
  // manufacturer's ID (e.g. Apple = 0x4C00)
  (unsigned byte)0xF0, (unsigned byte)0xFF,
  // 2 bytes, time
  (unsigned byte)0, (unsigned byte)0,
  // 2 bytes, distance 
  (unsigned byte)0, (unsigned byte)0 
};
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
oAdvertisementData.setManufacturerData(std::string(buff, 6));
pAdvertising->setAdvertisementData(oAdvertisementData);
Code for receiving end (ESPHome)
esp32_ble_tracker:
  on_ble_manufacturer_data_advertise:
    - mac_address: 4c:eb:d6:43:5d:82
      manufacturer_id: FFF0
      then:
        - lambda: |-
            uint16_t time = x[0] + (x[1] << 8);
            uint16_t dist = x[2] + (x[3] << 8);
            uint16_t oldTime = 0;
            uint16_t oldDist = 0;
            if (id(ble_xt285_helper_time_sensor).has_state() && !isnan(id(ble_xt285_helper_time_sensor).state))
              oldTime = id(ble_xt285_helper_time_sensor).state;
            if (id(ble_xt285_helper_distance_sensor).has_state() && !isnan(id(ble_xt285_helper_distance_sensor).state))
              oldDist = id(ble_xt285_helper_distance_sensor).state;
            if (time != oldTime || dist != oldDist) {
              id(ble_xt285_helper_time_sensor).publish_state(time);
              id(ble_xt285_helper_distance_sensor).publish_state(dist);
            }

sensor:
  - platform: template
    name: "BLE XT285 time"
    id: ble_xt285_helper_time_sensor
    accuracy_decimals: 0
  - platform: template
    name: "BLE XT285 distance"
    id: ble_xt285_helper_distance_sensor
    accuracy_decimals: 0

Thanks @slimak

Yesterday I was searching for the code I found, and now I’ve found it again. It’s the equivalent of what you are doing in Arduino code, but then in ESPHome yaml/lambda.

And a couple of comments earlier something that might work with a real advertisement.

I’ll try to see if the advertisement part works, if not there is only the misusing of the manufacturers ‘field’ until.

Also I don’t understand the broadcast property of a characteristic, and if it’s linked in any way with advertise

Hmm, I see that ashald added a commit on a copy of esphome-docs for esp32_ble, but it isn’t on the real repo.

esphome-docs/components/esp32_ble.rst at 42fe5663127f9233d157eb300d9d30af030e71e8 · ashald/esphome-docs

Related forum post, but is also based on manufacturer data/field:

Is BLE in itself uncapable of sending ‘random’ data, I think not because Xiaomi thermometers sending it. What I want looks like BTHome, but then in ESPHome yaml code.

Here another thing, in Arduino, not sure how it sends it:
Demonstrate BLE broadcast for connectionless data transfer