BLE characteristic read in ESPHome

I’m starting to program BLE on an ESP32 chip. Writing to a characteristic value works fine:

esp32_ble_server:
  services:
    - id: my_service
      uuid: ................
      characteristics:
        - id: my_characteristic
          uuid: ......................
          # allow writing from the client
          write: true
          on_write:
            then: ......

But I still don’t understand the “read” function — why do we use “read: true”?

esp32_ble_server:
  services:
    advertise: true
    characteristics:
      - id: test_characteristic
        uuid: .......
        read: true

Where does the value come from?
I tried using ble_server.characteristic.set_value, but it didn’t work.
I’m not sure if esp32_ble_server actually updates the value or if I’m misunderstanding how it works.

I also couldn’t find many examples of ESPHome using the newer BLE implementation — it seems pretty scarce.

Sorry, I’m not good at English :-))).

Unfortunately, none of us can help you find out why it didn’t work previously or what it would take to make it work because, you’ve provided us with 0% details, 0% code, 0% log outputs and etc. Kind of hard to offer someone suggestions for the problem they wont publish for us to look at.

You really need to slow down and go back to Start. Read the documentation, try to replicate and modify the simplest things with BT etc. Using the BT for Esphome is a bit complicated IMO and it wont work if you take shortcuts or you’re just guessing and hoping to make something work. Using Esphome BT has beat down many good men over the years and there’s just no easy stuff in here that people can use a copy/paste job from online and then move on. Unfortunately it just takes working, trial and error, and time practicing.

IMO the reason your not finding answers to your questions or examples of the Integrations is because you never read through the Esphome documentation or you went there and only looked at it briefly, otherwise you would have saw the answers to your questions and lots of examples.

I would suggest that you determine why you’re wanting to use BT for this task and then determine if BT is your only option for achieving whatever it is that your doing.

You likely may not even need to be using this BT method and there may be far better choices but, you didnt want anyone to know much about your project because its Top Secret perhaps?

I’ve been working on this for a while, but luckily I finally had some time to figure it out. :slightly_smiling_face:
Now I understand how it works — I think earlier I just had a mistake in the lambda part.

Enabling read: true allows reading data in two different ways:

1. Directly inside the characteristic definition:

esp32_ble_server:
  services:
    - uuid: ......... 
      characteristics:
        - uuid: ......... 
          read: true
          value:
            data: !lambda 'return DATA'

2. Using the set_value command:

esp32_ble_server:
  services:
    - uuid: ......... 
      characteristics:
        - uuid: ......... 
          read: true

        - ble_server.characteristic.set_value:
            id: .....
            value: DATA

However, both options cannot be used at the same time.

Right now, I’m programming a light switch. I tested it with the nRF Connect app on my phone, and so far it works.
Next, I’ll program another ESP32 as a client with a button.
Here’s my current server code:

esp32_ble_server:
  services:
    - uuid: .................
      characteristics:
        - uuid: .............
          id: ble_characteristic_upper 
          read: true
          write: true
          notify: true
          value:
            data: !lambda 'return std::vector<uint8_t>({(uint8_t)(id(light_upper).current_values.is_on() ? 1 : 0)});'
          on_write:
            then:
              - lambda: |-
                  if (x.size() > 0 && x[0] == 0x00) {
                    id(script_upper_off).execute();
                  } else if (x.size() > 0 && x[0] == 0x01) {
                    id(script_upper_on).execute();
                  }
script:
  - id: script_upper_off
    then:
      - light.turn_off: light_upper

  - id: script_upper_on
    then:
      - light.turn_on: light_upper

light:
  - platform: binary
    id: light_upper
    output: ......
    on_state: 
      then:
        - ble_server.characteristic.notify:
              id: ble_characteristic_upper

Do you have any suggestions for improvement? :slightly_smiling_face:
I’ll show the client code once I’m done — or if it turns out to be a complete mess. :smile:

Hi,
I managed to implement BLE communication between two devices with light state synchronization. You might wonder why the system is designed this way. The bedside light has its own button, but there is no Wi-Fi coverage in that area, so it cannot be controlled directly via Home Assistant. Near the door, there are two buttons controlling both the bedside light and the main room light, and this device has Wi-Fi connectivity.

Below, I’m sharing a part of the BLE code – both the server and client implementation. The button handling and other logic are not included here, as they are quite straightforward to implement.

Server:

substitutions:
  BLE_THIS_SERVICE:   "......uuid......"
  BLE_CHAR_UPPER:     "......uuid......"

esp32_ble_server:
  services:
    # light services
    - uuid: ${BLE_THIS_SERVICE}
      characteristics:
      # light UPPER
      - uuid: ${BLE_CHAR_UPPER}
        id: ble_characteristic_upper 
        read: true
        write: true
        notify: true
        value:
          data: !lambda 'return std::vector<uint8_t>({(uint8_t)(id(light_upper).current_values.is_on() ? 1 : 0)});'
        on_write:
          then:
            - lambda: |-
                if (x.size() > 0 && x[0] == 0x00) {
                  id(script_upper_off).execute();
                }else if (x.size() > 0 && x[0] == 0x01) {
                  id(script_upper_on).execute();
                }

script:
  - id: script_upper_off
    then:
      - light.turn_off: light_upper
  - id: script_upper_on
    then:
      - light.turn_on: light_upper
 
light:
  - platform: binary
    id: light_upper
    output: light_output_upper
    restore_mode: ALWAYS_OFF
    on_state: 
      then:
        - ble_server.characteristic.notify:
            id: ble_characteristic_upper

output:
  - platform: gpio
    pin: GPIO27
    id: light_output_upper

  - platform: gpio
    pin: GPIO14
    id: light_output_reader

Client:

substitutions:
  BLE_CLIENT_MAC:           "XX:XX:XX:XX:XX:XX"
  BLE_LIGHT_SERVICE:        "......uuid......"
  BLE_LIGHT_CHAR_UPPER:     "......uuid......"

ble_client:
  - mac_address: ${BLE_CLIENT_MAC}
    id: ble_device_light

sensor:
  - platform: ble_client
    id: ble_client_light_sensor_upper 
    ble_client_id: ble_device_light
    type: characteristic
    service_uuid: ${BLE_LIGHT_SERVICE}
    characteristic_uuid: ${BLE_LIGHT_CHAR_UPPER}
    notify: true
    update_interval: 30s
    internal: true
    lambda: |-
        if (x.size() > 0 && x[0] == 0x00) {
          id(script_light_upper_off).execute();
          return 0;
        }else if (x.size() > 0 && x[0] == 0x01) {
          id(script_light_upper_on).execute();
          return 1;
        }
  
light:
  - platform: binary
    id: light_upper
    output: light_output_upper

output:
  - platform: template
    type: binary
    id: light_output_upper
    write_action:
      - if:
          condition:
            lambda: 'return state != id(ble_client_light_sensor_upper).state;'
          then:
          - ble_client.ble_write:
              id: ble_device_light
              service_uuid: ${BLE_LIGHT_SERVICE}
              characteristic_uuid: ${BLE_LIGHT_CHAR_UPPER}
              value: !lambda "return std::vector<uint8_t>({ state ? 0x01 : 0x00 });"

script:
  - id: script_light_upper_off
    then:
      - light.turn_off: light_upper
  - id: script_light_upper_on
    then:
      - light.turn_on: light_upper

I’ll be glad if you share any improvements or suggestions for the code.