Help using BLE Client/Server

Hi, I’m really hoping someone could give me a bit of help with BLE.

I’ve managed to get a client ESP32 and a server ESP32 talking to each other and I can send and receive basic data, well just an integer, in a characteristic. However, I’m really struggling to use floating values. I’ve tried for days searching the web and to my regret using ChatGPT which waste huge amount of time as it’s so convincingly believable even when it horribly wrong – the worlds doomed!

I’m just showing the basic code trying to send a static floating number.

Server code:

esp32_ble_server:
  id: ble_server
  manufacturer: "Dave"
  manufacturer_data: [0xFF, 0xFF, 0xF1, 0x00]  
  services:
    uuid: "2a24b789-7aab-4535-af3e-ee76a35cc42d"
    characteristics:
    - id: tc1
      uuid: "cad48e28-7fbe-41cf-bae9-d77a6c233423"
      read: true
      value:
          data: 17.65
          type: float

I’ve tried so many other combinations, like trying to use bytebuffer, putting the value in quotes etc.
and Client code:

ble_client:
  - mac_address: 88:13:BF:6A:12:EE
    id: my_ble_client

sensor:
  - platform: ble_client
    type: characteristic
    ble_client_id: my_ble_client
    accuracy_decimals: 2
    service_uuid: "2a24b789-7aab-4535-af3e-ee76a35cc42d"
    characteristic_uuid: "cad48e28-7fbe-41cf-bae9-d77a6c233423"
    name: "My BLE Sensor"
    lambda: return *((float*)(&x[0]));

I either just get 0.00000 or some huge number. I did manage to send a 4-bye value once and it received just the first byte, but after so long I’ve just lost track of what I did and what I’m doing.

Any help, pointers or just a small snipped of code would be so welcome.

Many thanks
Dave

Any particular reason you dont just use BThome?

Hi JimmyHotcake02,

I’ve already got a couple of ESP32s running BTHome, but wanted to try this approach for 1) just to learn how it works and 2) because I believe BTHome is only one way.

I did get it all working very nicely now, just for reference:

This is how I set the Server

      value:
        data: "0.0"
        type: Float
        endianness: LITTLE

and dynamically updated the value in an interval using (might be a bit of overkill, but it works

      - lambda: |-
          static float float_value = 0.0;
          float_value += 0.01;
          std::string float_data(reinterpret_cast<const char*>(&float_value), 4);
          id(tc1).set_value(float_data);
          id(tc1).notify();
          const uint8_t* float_bytes = reinterpret_cast<const uint8_t*>(&float_value);
          ESP_LOGI("custom", "Float: %f Bytes: %02X %02X %02X %02X", float_value, float_bytes[0], float_bytes[1], float_bytes[2], float_bytes[3]);

and in the Client I used the following to read the foating data

      float value;
      memcpy(&value, x.data(), 4);
      ESP_LOGI("custom", "Float: %.4f Bytes: %02X %02X %02X %02X", value, x[0], x[1], x[2], x[3]);    
      return *((float*)(&x[0]));

I dont blame you for wanting to go this route at all! Its sort of like having to choose to control your lights from wled or using Esphome native integrations and lights arent the only thing you planned to add to that esp32 and same story with something like using Espresence or Esphome and etc. Sometimes your doing more than the 1 task and using a narrow use firmware that lacks many capabilities isn’t ideal or an option at all!

Very cool that you got it working and read my mind and posted it before i even had a chance to ask!

I was pretty sure you were going to have to do some back and forth conversations to convert the sensor states(float, int, string) etc to a hexadecimal, then transmit it via BT and convert from hexadecimal to desired format on the receiver esp32 or at least that’s my limited cave man understanding of what was needed and thats kind of what it looks like you are doing in your code plus using some arrays.

Is this code something you mostly understand or is it something you got elsewhere and worked for you? If its something you understand, do you mind if i pick your brain and ask a few questions about some parts of it?

I’m certainly no expert, not even close, but I’ve spent ages trying to understand the documentation, which is ok, but does leave a few gaps in understanding the approach at a holistic level, i.e. how things talk to each other and interact. It would have been nice to see a complete example, i.e. how does the BLE Tracker identify servers, using BLE Client to connect, use the BLE sensors to do something and then disconnect, etc. It’s all there like a jigsaw, and when you get it put together it all does make sense, but it did take me ages to get the complete view (or as best as I think I have). It works, but I’m certainly not sure I’ve done it right.

I’ve now got it working such that I can send data from the Server to the Client and display the data in ESPHome. Also use a button and a switch in ESPhome to send data back to the Server via the client. I can switch the ESP32 LED on and off from ESPHome using BT and via the Client. The Server is battery powered so no WiFi on it, which was one of my goals, having low power usage. The Client is powered and has WiFi on to talk to ESPHome (as a sort of home-made hub).

It’s all a bit uncontrolled at the moment, Servers just send stuff and the Client just receives stuff.

I’m now trying to get some semblance of control into the process, e.g. the Client uses BLE Tracker looking for Server advertisements, then for the Client to connect to the Server, read the data and disconnect. The Server will normally be in DeepSleep and will wake, read some sensors, advertise, send data when connected and then go back into DeepSleep.

So, I’ll certainly help, but as I said I’m no expert, just learning. I’m also old school developer using Ada, Pascal, Modula 2 and of course Basic. I can get by on C, C++, etc., but I’m slow, continually learning that as well. For quick ESP/Arduino programming I use B4R, but can do just enough (with online help) to use C.

I’ll certainly share stuff, maybe in return for some help on C++ :slight_smile:

Dave

I bet it has been ages if you used to use things like Basic and other languages that were popular while the dinosaurs still walked the earth!!

Oh geeze… That was a horrible joke and probably not doing myself any favors with the guy i asked for his help… lol

I get it though, im a documentation guru myself. Youll see me here and other forum’s preaching to others that they should go read some scripture from the documentation all the time!

I understand this is just adding 0.01 and incrementing float_value but, it’s unclear to me why your doing this.

float_value += 0.01;

Im also unclear avout what this does.

id(tc1).notify();

I probably have more questions but. Ill have to play around with this for a little bit first.

Joke taken :grinning: although Pascal is a far better language, but I don’t want to start a debate :wink:. C++ looks like a spider walked across the page after stepping in ink :rofl:

So, jokes over and I may come to regret my comment about C++ :thinking:

I understand this is just adding 0.01 and incrementing float_value but, it’s unclear to me why your doing this.
float_value += 0.01;

This is just for testing, i.e. to have a value that I can see increments – I also use nRF Connect on my phone to look at BT devices, very useful tool.

Im also unclear avout what this does.
id(tc1).notify();

As far as I can tell, this tells the Server to send out a notification (not sure if this means that the Server ‘advertises’ or if that is something different). If I don’t do it, then the Client doesn’t see anything when I update the value in the Server.

Dave

Oh, too late now, those are fighting words! :wink:

Wow, thats actually a pretty good description. I like learning c++ as a hobby but, if it werent for those dang { } symbols all over causing me errors, i wouldnt’t have so many keywords stuck in the drywall of my work room!

I saw someone else use this in a piece of code for something else and he basically said the same thing and even his wouldn’t work without automatically doing increments like that. I’d be lying if i said i fully understand it but, ill take your word and play around and do some trial and error. Thats the best method for learning for me, i gotta do it and then see it…

Ya, i guess i kind of assumed that, i just dony recall a function like that in the docs so just curious.

Thank you for taking the time to reply amd explain those, i appreciate it. If you dont mind and if you remember. Will you come back here and update this if you make more progress or just think something is interesting, im sure it would be to many people!

Take Care!

OK as suggested/requested I’ve included my code so far below. Please note it is mainly just test/experiment code allowing me to try to understand how it all works. Setup involves 2 ESP32s, a regular one and a C3. The regular one runs with both WiFi and BLE on and talks to HA through ESPHome and the C3 just has BLE.

In the example, the server has 1 Service and 1 Characteristic - I’ve not bothered with Descriptors. It does 2 things, 1) starts Advertising and 2) waits for a command from the Client using on_write.

When the Client connects, it uses the on_connect to send a simple “ESP32C3 Hello” back to the Client using notify. Note it waits 4s for the connection to stabilise - I’m not too sure about this, but it is needed and anything less than 4s (ish) doesn’t work.

Note as far as I understand the Server starts advertising when successfully configured and no client is connected and stops when a client is connected, you cannot control this in code.

The Client is setup (see later) to use a HA switch to send a command “on” or “off” to the Server and the Server will see this using the on_write. It then simply switches its onboard LED on or off and sends back (using notify) an acknowledge command.

The Client is waiting for Advertisements using BLE Tracker and when it sees one it connects using the on_ble_advertise (obviously you would want to do more to check things in here, but it’s just for testing). Apparently, MAC addresses need to be static so I can’t find a way to look for any Server and although I can get its MAC address I don’t see a way of then using it in communications. Note also that it resets a timer, explained later.

It then uses the text sensor on_notify to get the data and at the moment just send it to the serial interface. It then disconnects. Note it resets the timer and also starts the scanning. It also starts scanning again as it was stopped when connected in the on_ble_advertise - note I found I had to stop scanning or it wouldn’t connect.

I had a problem with the approach hanging if the Server was reset because the Client sometimes got caught thinking the Server was connected and never started scanning again, hence the interval and the timer. This just checks every so often to see if we think we are connected but haven’t had any data for a while. Of course, this would need to be properly setup for a particular situation, but just here for testing.

The Client also has a switch which is use to send “on” "off to the Server to turn on or off the Server LED,

Code below and I hope it is useful to someone. Please no comments about my coding, al lof this is just to learn and I’m sure I’ve got things wrong.

Server Code

substitutions:
  serviceUUID: '2a24b789-7aab-4535-af3e-ee76a35cc42d'
  characteristicID_float: 'cad48e28-7fbe-41cf-bae9-d77a6c233423'
  characteristicID_uint16: 'ea6b6666-1171-4c8a-bc42-bc97bfcda1b0'
  characteristicID_string: '43267d33-f52f-4364-854b-a5cf614e3552'

esphome:
  name: sm-ble-1
  friendly_name: SM BLE 1
  on_boot:
    priority: -100
    then:
      - repeat:
          count: 5
          then:
            - output.turn_on: led
            - delay: 100ms
            - output.turn_off: led
            - delay: 100ms

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: arduino
logger:
  level: INFO

output:
  - platform: gpio
    pin: GPIO8
    id: led
    inverted: true

esp32_ble_server:
  id: ble_server
  manufacturer: "Dave"
  manufacturer_data: [0xFF, 0xFF, 0xF1, 0x00]  
  services:
    uuid: ${serviceUUID}
    advertise: True
    characteristics:
    - id: tc3
      uuid: ${characteristicID_string}
      notify: True
      read: True
      write: True
      value:
        data: "Waiting..."
        type: string
      on_write: 
        then:
          - lambda: |-
              // x is a std::vector<uint8_t>, convert it to a std::sring and the for the ESP32 use .c_str() to get it in the right format
              std::string cmd(x.begin(), x.end());
              ESP_LOGI("ESP32C3", "Command received: %s from client %d", cmd.c_str(), id);
              auto send_ack = [&]() {
                std::string ack = "ack";
                id(tc3).set_value(ack);
                id(tc3).notify();
                ESP_LOGI("ESP32C3", "Telling client %d we've accepted the command", id);
              };              
              if (cmd == "on") 
              {
                ESP_LOGI("ESP32C3", "Turning ON");
                id(led).turn_on();  // Turns ON the LED (pulls GPIO8 LOW due to inverted: true)
                send_ack;                
              }
              else if (cmd == "off")
              {
                ESP_LOGI("ESP32C3", "Turning OFF");
                id(led).turn_off();  // Turns ON the LED (pulls GPIO8 LOW due to inverted: true)                
                send_ack;
              }
  on_connect: 
    then:
      - lambda: |-
          ESP_LOGI("ESP32C3", "Client connected, waiting before notify...");
      - delay: 4s
      - lambda: |-
          ESP_LOGI("ESP32C3", "Client connected, sending notify");
          std::string string_value = "ESP32C3 Hello";
          id(tc3).set_value(string_value);
          id(tc3).notify();
  on_disconnect:
    - lambda: |-
        ESP_LOGI("ESP32C3", "Client disconnected");

Client code

substitutions:
  serviceUUID: '2a24b789-7aab-4535-af3e-ee76a35cc42d'
  characteristicID_float: 'cad48e28-7fbe-41cf-bae9-d77a6c233423'
  characteristicID_uint16: 'ea6b6666-1171-4c8a-bc42-bc97bfcda1b0'
  characteristicID_string: '43267d33-f52f-4364-854b-a5cf614e3552'

esphome:
  name: bleclient
  friendly_name: BLEClient
esp32:
  board: esp32dev
  framework:
    type: arduino
logger:
  level: INFO
api:
  encryption:
    key: "w3xsT7cJ7WjWuOy5FnfpRPcXGPvdXbwoE3sJLk5Dpvk="
ota:
  - platform: esphome
    password: "24ea9cfd4f62a00fdedd53397c962c62"
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "Bletest Fallback Hotspot"
    password: "pJjhkhCKXNf9"

esp32_ble_tracker:
  scan_parameters:
    interval: 320ms
    window: 30ms
    active: true
  on_ble_advertise:
    - mac_address: 98:88:E0:CB:11:E2
      then:
        - esp32_ble_tracker.stop_scan: {}        
        - lambda: |-
            ESP_LOGI("Client", "MAC: %s, RSSI: %d", x.address_str().c_str(), x.get_rssi());
            for (auto uuid : x.get_service_uuids()) {
              ESP_LOGI("Client", "Advertised UUID: %s", uuid.to_string().c_str());
            }
            id(last_data_time) = millis();
            id(myESP32_C3_Super_Mini).connect();            

ble_client:
  - mac_address: 98:88:E0:CB:11:E2
    id: myESP32_C3_Super_Mini
    auto_connect: false

switch:
  - platform: template
    name: "Send on/off to ESP32-C3-Super-Mini"
    id: esp32_c3_super_mini_switch
    optimistic: true
    turn_on_action:
      - ble_client.ble_write:
          id: myESP32_C3_Super_Mini
          service_uuid: ${serviceUUID}
          characteristic_uuid: ${characteristicID_string}
          value: !lambda |-
            std::string my_string = "on";
            ESP_LOGI("Client", "Sending: %s", my_string.c_str());
            return std::vector<uint8_t>(my_string.begin(), my_string.end());
    turn_off_action:
      - ble_client.ble_write:
          id: myESP32_C3_Super_Mini
          service_uuid: ${serviceUUID}
          characteristic_uuid: ${characteristicID_string}
          value: !lambda |-
            std::string my_string = "off";
            ESP_LOGI("Client", "Sending: %s", my_string.c_str());
            return std::vector<uint8_t>(my_string.begin(), my_string.end());

text_sensor:
  - platform: ble_client
    ble_client_id: myESP32_C3_Super_Mini
    name: "myESP32 C3 Super Mini Command Acknowledge"
    service_uuid: ${serviceUUID}
    characteristic_uuid: ${characteristicID_string}
    id: myESP32_C3_Super_Mini_command_acknowleledge
    notify: true
    update_interval: never
    on_notify: 
      then:
        - lambda: |-
            id(last_data_time) = millis();
            std::string output = "Notify received, " + std::to_string(x.size()) + " bytes:";
            for (size_t i = 0; i < x.size(); i++) {
              char buf[6];
              snprintf(buf, sizeof(buf), " 0x%02X", (uint8_t)x[i]);
              output += buf;
            }
            ESP_LOGI("Client", "%s", output.c_str());
            std::string s(x.begin(), x.end());
            ESP_LOGI("Client", "String: %s", s.c_str());
            id(myESP32_C3_Super_Mini).disconnect();                      
        - esp32_ble_tracker.start_scan: {}

interval:
  - interval: 15s  # Check every 15 seconds
    then:
      - if:
          condition:
            lambda: |-
              // If we think we're connected but haven't received data recently
              return id(myESP32_C3_Super_Mini).connected() && 
                     id(last_data_time) > 0 &&
                     (millis() - id(last_data_time)) > 20000;  // 20 seconds without data
          then:
            lambda: |-
              ESP_LOGI("Client","Stale connection detected, forcing disconnect");
              id(myESP32_C3_Super_Mini).disconnect();
            #- logger.log: "Stale connection detected, forcing disconnect"
            #- ble_client.disconnect: myESP32_C3_Super_Mini
      - if:
          condition:
            lambda: 'return !id(myESP32_C3_Super_Mini).connected();'
          then:
            - lambda: |-
                ESP_LOGI("Client","Not connected, start scan");
            - esp32_ble_tracker.start_scan: {}

globals:
  - id: last_data_time
    type: unsigned long
    initial_value: '0'

Hi,

Hope someone can cast an eye of a small problem I have with the code below. It all seems to work rather well, but looking at the Client log I sometimes, but not always, see a spurious 0.00 a few seocnds before the true value.

The setup is a little different from the preivous code. I now have 2 ESP32-C servers, one notifying 3 Chacateristics, a floating value, an integer value and a string and the second notifying just a floating value, using the same Characteirstci UUID as the first floating value Characteristic.

Float, Integer and String come from the first ESP32-C3 and Float 2 comes form the second. Also, the first one is just using an interval to send the data whereas the second is using deepsleep. The String works perfectly.

Any help would be very much appreciated

Here is the log:

[14:15:34][I][ble_sensor:034]: [My BLE Sensor floating Value 2] Connected successfully!
[14:15:36][I][esp32_ble_client:339]: [2] [98:88:E0:CB:15:06] Connected
[14:15:38][I][custom:112]: Float: 0.0000 Bytes: 00 00 00 00
[14:15:39][W][component:203]: Component ble_client.sensor cleared Warning flag
[14:15:39][I][custom:128]: Float 2: 0.0000 Bytes: 00 00 00 00
[14:15:44][I][custom:143]: Integer: 0 Bytes: 00 00
[14:15:44][I][custom:128]: Float 2: 2.9268 Bytes: 34 50 3B 40
[14:15:51][I][custom:112]: Float: 5.3900 Bytes: 1D 7B AC 40
[14:15:51][I][custom:143]: Integer: 539 Bytes: 1B 02
[14:15:51][I][custom:162]: String from server: This is a string and integer 539

Here is the Cliennt Sensors

sensor:
  - platform: ble_client
    type: characteristic  
    ble_client_id: my_ble_server_floating_integer_string_value
    name: "My BLE Sensor floating Value"
    service_uuid: ${serviceUUID}
    characteristic_uuid: ${characteristicID_float}
    id: my_ble_sensor_floating_value
    accuracy_decimals: 2  # ← this is the key bit    
    notify: true
    lambda: |-
      float value;
      memcpy(&value, x.data(), 4);
      ESP_LOGI("custom", "Float: %.4f Bytes: %02X %02X %02X %02X", value, x[0], x[1], x[2], x[3]);    
      return value;
      //return *((float*)(&x[0]));

  - platform: ble_client
    type: characteristic  
    ble_client_id: myESP32_C3_Super_Mini_2
    name: "My BLE Sensor floating Value 2"
    service_uuid: ${serviceUUID}
    characteristic_uuid: ${characteristicID_float}
    id: my_ble_sensor_floating_value_2
    accuracy_decimals: 2  # ← this is the key bit
    notify: true
    lambda: |-
      float value;
      memcpy(&value, x.data(), 4);
      ESP_LOGI("custom", "Float 2: %.4f Bytes: %02X %02X %02X %02X", value, x[0], x[1], x[2], x[3]);    
      return value;
      //return *((float*)(&x[0]));


  - platform: ble_client
    type: characteristic  
    ble_client_id: my_ble_server_floating_integer_string_value
    name: "My BLE Sensor integer Value"
    service_uuid: ${serviceUUID}
    characteristic_uuid: ${characteristicID_uint16}
    id: my_ble_sensor_integer_value
    notify: true
    lambda: |-
      uint16_t value = x[0] | (x[1] << 8);  // Assuming little-endian
      ESP_LOGI("custom", "Integer: %u Bytes: %02X %02X", value, x[0], x[1]);
      return (float)value;