Example on setting up esp32 as BLE server?

Hey I am looking around to have my esp32 broadcast its values to HA via Bluetooth. It is interesting due to low energy usage, possible in Arduino but hard to get any clue an setting this up with Esphome. Except the fact that I can use BLE_SERVER … anyone who does have a working example?

2 Likes

Not of communicating with HA via BLE.

The other way, eg BLE Device<–BLE–>ESP32<–Wifi–>HA is what the BLE capabilities in esphome are all about.

There are 2 ways - at least that I know about - how you can communicate:

  1. Advertisements - connection-less, max 31 bytes of payload
  2. Client-server - requires connection, up to 512 bytes.

I tried both of them and working on a blog post with more details, but in I’ll try to cover them here briefly.
One thing I’d note - I’d agree with the above comment is that communicating data directly to HA is somewhat cumbersome. It’s much more convenient to use another ESP32 that will bridge BLE to HA.

In my case, I have 2 nodes: a transmitter, and receiver.
Transmitter is on wired power supply, connected to Wi-Fi, pulls data from HA, and exposes them over BLE.
The receiver powered by a battery, wakes up every interval, receives data to show on eInk display, and goes back to deep sleep.

I recently contributed a small change to ESPHome that allows you to embed payload into BLE advertisements. It’s available in the latest release so you can start using it right away. Since ti requires a bit of coding, maintainers didn’t want to include the guide into the docs, but the guide itself is still on GIthub esphome-docs/esp32_ble.rst at 42fe5663127f9233d157eb300d9d30af030e71e8 · ashald/esphome-docs · GitHub - it’s a full example of transmitting data over BLE advertisements, and consuming them. In theory, you should be able to receive the advertisements with HA, but IDK what it’d take to make existing BLE integrations to process custom data [so I’d suggest just using 2x EP32].

If you want to use the client-server mode that can handle more data, there is an excellent ESP32 custom component wifwucite/esphome-ble-controller which can expose any sensor value as a BLE characteristic. To consume them via ESP32, you can use the built-in ble_client sensor, or text_sensor that O contributed as well (I think it’s already available in the latest release, but docs haven’t been updated yet so you can see them here esphome-docs/ble_client.rst at efbd53774d5a64f114cbd1a5989e6b8453ed2bfa · ashald/esphome-docs · GitHub). Alternatively, you can use myhomeiot/esphome-components which provides a slightly more convenient client in my opinion, which I used in my prototype.
Again, there should be an integration for HA that could connect to a BLE server, but I’m not aware of any, and IDK what it’d take to make it work with custom services and characteristics.

Hope this helps, and let me know if you’d have any questions.

9 Likes

Nice work, tx for the reaction and the links provided. I will dig into this solution with only using the manufacturer data string. Keep you posted!

Hi,

Thanks for work on this. I’ve scratching my head to make this esp32_ble: example working but the

id(ble)->get_advertising()

always returns null.

Do i need another component like esp32_ble_server or do i need to init something else?

Thank you!

1 Like

Hi @ashald

Your work is a very welcome oasis in a google search desert :grinning:

I have tried to implement this to simulate a BLE temperature sensor to be received by a Viltron controller, the code works up to this line.

          // Force esp_ble to use new data
          id(ble).get_advertising()->start();

This causes a core panic. No Idea why and I don’t know how to decode a backtrace from esphome ,

If I rem the above line it boots normally, but obviously doesn’t transmit any ble signal

Logs are here:

ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:1184
load:0x40078000,len:13132
load:0x40080400,len:3036
entry 0x400805e4
[I][logger:262]: Log initialized
[C][ota:469]: There have been 0 suspected unsuccessful boot attempts.
[D][esp32.preferences:114]: Saving 1 preferences to flash...
[D][esp32.preferences:143]: Saving 1 preferences to flash: 0 cached, 1 written, 0 failed
[I][app:029]: Running through setup()...
[C][dht:011]: Setting up DHT...
[C][esp32_ble:027]: Setting up BLE...
[D][esp32_ble:043]: BLE setup complete
[C][wifi:038]: Setting up WiFi...
[C][wifi:039]:   Local MAC: 30:AE:A4:8B:A8:4C
[D][wifi:387]: Starting scan...
[D][advertisement:068]: Refreshing advertisement
Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

Core  1 register dump:
PC      : 0x400d86ca  PS      : 0x00060b30  A0      : 0x800dfb00  A1      : 0x3ffcd410  
A2      : 0x00000000  A3      : 0x00000000  A4      : 0x3ffc3c44  A5      : 0x00000000  
A6      : 0x00000000  A7      : 0x003fffff  A8      : 0x00000000  A9      : 0x00000001  
A10     : 0x3ffcd480  A11     : 0x00000000  A12     : 0x0000000c  A13     : 0x3ffcd48c  
A14     : 0x3ffc0230  A15     : 0x00000000  SAR     : 0x0000001d  EXCCAUSE: 0x0000001c  
EXCVADDR: 0x0000007c  LBEG    : 0x400913cc  LEND    : 0x400913d7  LCOUNT  : 0xffffffff  


Backtrace:0x400d86c7:0x3ffcd4100x400dfafd:0x3ffcd470 0x400dfa4d:0x3ffcd4b0 0x401f3f47:0x3ffcd4d0 0x401f3f77:0x3ffcd4f0 0x401f3de1:0x3ffcd510 0x400df993:0x3ffcd530 0x400de7c2:0x3ffcd580 0x400e061e:0x3ffcd5b0 0x400e646e:0x3ffcd6c0 




ELF file SHA256: 0000000000000000

Rebooting...

YAML is here

esphome:
  name: ble-server
  includes:
  - ble-server-structs.h

esp32_ble:
    id: ble

esp32:
  board: esp32dev
  framework:
    type: arduino

time:
  - platform: sntp
    id: ntp

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "REDACTED"

ota:
  password: "REDACTED"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "ble-server Fallback Hotspot"
    password: "REDACTED"

captive_portal:
    


sensor:
  - platform: dht
    pin: GPIO16
    temperature:
      id: ble_server_temperature
      name: "Ble Server Temperature"
      filters:
      - or:
        - throttle: 10s
        - delta: 0.2
      device_class: temperature
      state_class: measurement
      unit_of_measurement: °C
    humidity:
      id: ble_server_humidity
      name: "Ble Server Humidity"
      filters:
      - delta: 2
      device_class: humidity
      state_class: measurement
      unit_of_measurement: "%"
    update_interval: 10s



interval:
  - interval: 10seconds
    then:
      - lambda: |-
          ESP_LOGD("advertisement", "Refreshing advertisement");

          // We allocate single instance in memory and refresh it in place
          static manufacturer_data_t* advertisement = new manufacturer_data_t;

          // Assuming there is a time component configured with identifier ntp
          advertisement->data.timestamp = id(ntp).now().timestamp;

          // Assuming there is a sensor component configured with identifier ble_server_temperature
          advertisement->data.ble_server_temperature = (uint16_t) id(ble_server_temperature).state;

          if (advertisement->company_id == 0) { // Execute once after boot for initial setup
              ESP_LOGD("advertisement", "Initializing advertisement");

              advertisement->company_id = 0xFFFF; // 0xFFFF reserved for local testing and non-commercial use
              id(ble).get_advertising()->set_manufacturer_data((uint8_t*) advertisement, sizeof(manufacturer_data_t));

              ESP_LOGD("main", "Advertisement initialized");
          }

          // Force esp_ble to use new data
          id(ble).get_advertising()->start();

          ESP_LOGD("advertisement", "Advertisement refreshed");


ble-server-structs.h

typedef struct payload {

  time_t   timestamp;
  uint16_t ble_server_temperature;

} __attribute__((packed)) payload_t;


typedef struct manufacturer_data {

  uint16_t   company_id;
  payload_t  data;

} manufacturer_data_t;

Any idea what I am doing wrong?

Hi @anthonykeane, from a glance, you’re missing

esp32_ble_server:

from your YAML that would pull-in server-related BLE dependencies.

As a follow-up, I’d just mention that I found the above setup working perfectly when I have a constantly powered server, and a client that has to consume data quickly every now and then. If both transmitter and receiver have constant power, you might want to look into BTHome or ESP-Now based solutions - I think both are widely mentioned in ESPHome discord (#show-off) and docs.

@ashald Thank you, It works well now.

I really appreciate your help,

Any chance you could update the docs so the example has esp32_ble_server: included?

Hi, I wanted to create an esp32 water level sensor to run from a battery.
I was hoping to use BLE and although I have enabled esp32_ble_server: in my yaml. Home assistant doesn’t find it. but my phone does.

My home assistant is a raspberry pi4 which does have Bluetooth and is added as a device within HA.

is there something I’m doing wrong? can someone point me in the right direction?

The “esp32_ble_server” is for other components to use it as a base for them to expose thier entity but that does not mean it shows up in HA. You could use “esphome-ble-controller” and a second esp connected to WIFI and main’s power to publish it to HA

Hi
Unfortunately this now fails I guess, because of some code changes around the BLE improv push

Any idea on how to get advertising of data working again without needing to go through client/server connection?

Thanks!

Ok. To follow up my earlier message, I have successfully managed to get advertising of state data within Manufacturer data working correctly. This allows sending data, and also reading of data on IOS which locks downs all advertised attributes except manufacturer data.

Using latest esphome 2023.10.6

No need for structs or additional files

Make sure ble_server enabled. Note here this has id: ble

esp32_ble_server:
  id: ble
  manufacturer_data: [0xFF, 0xFF, 0xF1, 0x00]

&&

time:
  - platform: sntp
    id: ntp  
    on_time:
    - seconds: 0,10
      then:
        - lambda: |-
            ESP_LOGD("main", "Advertisement updated");           
            float f_value = id(levelmetres).state;
            uint8_t *float_data = (uint8_t*)&f_value;
            ESP_LOGD("main", "Sending updated Level %f", id(levelmetres).state );
            id(ble).set_manufacturer_data({ 0xFF, 0xFF, 0xF9, float_data[0], float_data[1], float_data[2], float_data[3]});

Using time, repeating every 10 seconds.
levelmetres = is a template level sensor.
The rest convert the float value to hex chars and updates the manufacturer data, with the values.
To read back convert the values after 0xF9 to float using Little endian.

Glenn

1 Like

Hi, I can’t have esphome to compile. I get a error on get_advertising()

error: ‘class esphome::esp32_ble::ESP32BLE’ has no member named ‘get_advertising’; did you mean ‘advertising_’?

Is that still working for you?

How do you get this information with ESP32 esphome ble client?

It tells you in the docs how to set up devices, scan for devices, etc…

I understand the concept but Im not sure how to write a lambda code to “convert the values after 0xF9 to float using Little endian.”

That’s what I see in the Logs:

[13:59:43][VV][esp32_ble_tracker:440]: Address: 68:B6:B3:AF:9A:22 (PUBLIC)
[13:59:43][VV][esp32_ble_tracker:442]: RSSI: -58
[13:59:43][VV][esp32_ble_tracker:443]: Name: ‘’
[13:59:43][VV][esp32_ble_tracker:451]: Ad Flag: 6
[13:59:43][VV][esp32_ble_tracker:457]: Manufacturer data: F9.A4.55.F8.42 (5)
[13:59:43][VV][esp32_ble_tracker:473]: Adv data: 02.01.06.08.FF.FF.FF.F9.A4.55.F8.42 (12)

So the msg is being advertised, I know the MAC address. Seems like I need manufacture_id for ble_client which I assume is FFFF in this case. The manufacture data is there.

I see this example on esphome website that would update a template sensor with the BLE value above, just don’t know how to configure.

esp32_ble_tracker:
  on_ble_manufacturer_data_advertise:
    - mac_address: 68:B6:B3:AF:9A:22
      manufacturer_id: FFFF
      then:
        - lambda: |-
            if (x[0] != 0x7b || x[1] != 0x61) return;
            int value = x[2] + (x[3] << 8);
            id(ble_sensor).publish_state(value);

Thank you!

Yaml please, not gibberish.

I have tried this:

  on_ble_manufacturer_data_advertise:
    - mac_address: 68:B6:B3:AF:9A:22
      manufacturer_id: FFFF
      then:
        - lambda: |-
            int value = x[1] + (x[2] << 8) + (x[3] << 16) + (x[3] << 24);
            id(ble_sensor).publish_state(value);

But Im getting wrong values:

[17:10:14][VV][esp32_ble_tracker:440]:   Address: 68:B6:B3:AF:9A:22 (PUBLIC)
[17:10:14][VV][esp32_ble_tracker:442]:   RSSI: -60
[17:10:14][VV][esp32_ble_tracker:443]:   Name: ''
[17:10:14][VV][esp32_ble_tracker:451]:   Ad Flag: 6
[17:10:14][VV][esp32_ble_tracker:457]:   Manufacturer data: F9.74.A5.F7.42 (5)
[17:10:14][VV][esp32_ble_tracker:473]: Adv data: 02.01.06.08.FF.FF.FF.F9.74.A5.F7.42 (12)
[17:10:14][V][sensor:043]: 'Test Voltage': Received new state -134765200.000000
[17:10:14][D][sensor:094]: 'Test Voltage': Sending state -134765200.00000 V with 0 decimals of accuracy
[17:10:14][VV][api.service:140]: send_sensor_state_response: SensorStateResponse {
  key: 3719805054
  state: -1.34765e+08
  missing_state: NO
}

Im picking up the BLE transmitted Hex values just not getting the decimal conversion right.
Thank you!

Have you asked the people in the discord server?