Sonoff BLE / R5 guidance

I’m looking for some guidance on getting the Sonoff R5 switch working with ESPHome / HA.

My understanding is flashing an ESP32 with code supplied above (here is my code):

substitutions:
  devicename: esphome-esp32-8099e4

esphome:
  name: $devicename
  friendly_name: Sonoff R5 BT Receiver

esp32:
  board: esp32dev
  framework:
    type: esp-idf

wifi:
  power_save_mode: LIGHT
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "devicename"
    password: !secret esphome_ap_password

api:
  homeassistant_services: true

ota:
  - platform: esphome

web_server:

logger:

captive_portal:


globals:
  - id: btdata_uuid4
    type: std::vector<uint32_t>
    restore_value: no
    #button press UUID by order. The order may vary according to device version
    initial_value: '{0x8A6BECDA, 0xD535B385, 0x1FFC794F, 0xE60781B6, 0x2FCF487F, 0xD734B087, 0x02E36652, 0xEB0B8FBB, 0x32D15662,
                     0xFA1B99AA, 0x9575F6C5, 0x9F7CFCCF, 0x29C84B79, 0xBF5FDDEF, 0xA043C2F0, 0xBF5EDAEF, 0xA141C4F1, 0x9D7EF8CD}'
  - id: button_actions
    type: std::vector<std::string>
    restore_value: no
    initial_value: '{"short", "double", "long"}'

binary_sensor: # to stop BLE scanner while WiFi/API not connected
  - platform: template
    id: ble_scan_torun
    internal: true
    entity_category: diagnostic
    device_class: connectivity
    lambda: !lambda |-
      return global_api_server->is_connected();
    on_press:
    - esp32_ble_tracker.start_scan:
        continuous: true
    on_release:
    - esp32_ble_tracker.stop_scan:

esp32_ble_tracker:
  scan_parameters:
    interval: 150ms
    window: 150ms
    active: true
    continuous : true

  on_ble_advertise:
    - mac_address: "66:55:44:33:22:11" # Sonoff
      then:
        - lambda: |-
            static std::vector< esphome::esp32_ble::ESPBTUUID > btdata_store;
            auto btdata = x.get_service_uuids();
            ESP_LOGD("Sonoff", "data received");

            if (btdata.size() >= 6 && btdata[1].contains(0x78, 0xF6)) {
              if (btdata_store == btdata) {
                ESP_LOGD("Sonoff", "---- duplicate");
              } else {
                btdata_store = btdata;
                uint32_t btdevice = (btdata[2].get_uuid().uuid.uuid32 & 0xff000000) |
                                    (btdata[3].get_uuid().uuid.uuid32 & 0x00ffffff);
                uint32_t uuid4u32 = (btdata[4].get_uuid().uuid.uuid32);
                ESP_LOGD("Sonoff", "0x%x : 0x%x", btdevice, uuid4u32);

                for (size_t i = 0; i < id(btdata_uuid4).size(); i++) {
                  uint32_t stored_value = id(btdata_uuid4)[i];
                  if ((((uuid4u32 >> 24) ^ (stored_value >> 24)) == ((uuid4u32 >> 16 & 0xff) ^ (stored_value >> 16 & 0xff))) &&
                      (((uuid4u32 >> 8 & 0xff) ^ (stored_value >> 8 & 0xff)) == ((uuid4u32 & 0xff) ^ (stored_value & 0xff)))) {

                    int button_number = i / 3 + 1;  // each button is grouped in multiples of
                    int button_action = i % 3;

                    esphome::api::CustomAPIDevice capi;
                    capi.fire_homeassistant_event("esphome.sonoff_ble",
                      {{"device", format_hex(btdevice)},
                       {"button", to_string(button_number)},
                       {"action", id(button_actions)[button_action].c_str()},
                       {"service", format_hex(uuid4u32)}}
                    );
                    ESP_LOGI("Sonoff", "0x%x : %i : %s", btdevice, button_number,id(button_actions)[button_action].c_str());
                    return;
                  }
                }
              }
            }

The ESP32 can then act as a BT receiver for Sonoff R5.

automation:
    - id: sonoff_ble_events
      alias: "Sonoff BLE events"
      description: ''
      mode: restart
      trigger:
        - platform: event
          event_type: esphome.sonoff_ble
      condition: []
      variables:
        data: "{{ trigger.event.data }}"
      action:
      - choose:
        - conditions: "{{ data.device == '5a2bcfd9' }}"
          sequence:
            - choose:
              - conditions: "{{ data.button == '1' and data.action == 'short' }}"
                sequence:
                  - action: switch.toggle
                    target:
                      entity_id:
                        - switch.xxx

This is then added to an automation.

My assumtion would be via Settings > Automation > Create Automation

I’m sure I don’t have enough understanding on how this should make to get the correct result. Any assistance would be appreciated.

Try my YAML, it publish 6 Button as binary sensor.

esphome:
  name: "name"
  friendly_name: "Name"
  on_boot:
    priority: -100
    then:
      - lambda: |-
          id(button1_short).publish_state(false);
          id(button1_double).publish_state(false);
          id(button1_long).publish_state(false);
          id(button2_short).publish_state(false);
          id(button2_double).publish_state(false);
          id(button2_long).publish_state(false);
          id(button3_short).publish_state(false);
          id(button3_double).publish_state(false);
          id(button3_long).publish_state(false);
          id(button4_short).publish_state(false);
          id(button4_double).publish_state(false);
          id(button4_long).publish_state(false);
          id(button5_short).publish_state(false);
          id(button5_double).publish_state(false);
          id(button5_long).publish_state(false);
          id(button6_short).publish_state(false);
          id(button6_double).publish_state(false);
          id(button6_long).publish_state(false);

globals:
  - id: btdata_uuid4
    type: std::vector<uint32_t>
    restore_value: no
    #button press UUID by order. The order may vary according to device version
    initial_value: '{0x8A6BECDA, 0xD535B385, 0x1FFC794F, 0xE60781B6, 0x2FCF487F, 0xD734B087, 0x02E36652, 0xEB0B8FBB, 0x32D15662,
                     0xFA1B99AA, 0x9575F6C5, 0x9F7CFCCF, 0x29C84B79, 0xBF5FDDEF, 0xA043C2F0, 0xBF5EDAEF, 0xA141C4F1, 0x9D7EF8CD}'
  - id: button_actions
    type: std::vector<std::string>
    restore_value: no
    initial_value: '{"short", "double", "long"}'

esp32_ble_tracker:
  scan_parameters:
    interval: 150ms
    window: 150ms
    active: true
    continuous : true
  on_ble_advertise:
    - mac_address: "66:55:44:33:22:11" # Sonoff
      then:
        - lambda: |-
            static std::vector<esphome::esp32_ble::ESPBTUUID> btdata_store;
            auto btdata = x.get_service_uuids();
            ESP_LOGD("Sonoff", "data received");
            if (btdata.size() >= 6 && btdata[1].contains(0x78, 0xF6)) {
              if (btdata_store == btdata) {
                ESP_LOGD("Sonoff", "---- duplicate");
                return;
              }
              btdata_store = btdata;
              uint32_t btdevice = (btdata[2].get_uuid().uuid.uuid32 & 0xff000000) |
                                  (btdata[3].get_uuid().uuid.uuid32 & 0x00ffffff);
              uint32_t uuid4u32 = (btdata[4].get_uuid().uuid.uuid32);
              ESP_LOGD("Sonoff", "0x%x : 0x%x", btdevice, uuid4u32);
              for (size_t i = 0; i < id(btdata_uuid4).size(); i++) {
                uint32_t stored_value = id(btdata_uuid4)[i];
                if ((((uuid4u32 >> 24) ^ (stored_value >> 24)) == ((uuid4u32 >> 16 & 0xff) ^ (stored_value >> 16 & 0xff))) &&
                    (((uuid4u32 >> 8 & 0xff) ^ (stored_value >> 8 & 0xff)) == ((uuid4u32 & 0xff) ^ (stored_value & 0xff)))) {
                  int button_number = i / 3 + 1;
                  int button_action = i % 3;
                  esphome::api::CustomAPIDevice capi;
                  capi.fire_homeassistant_event("esphome.sonoff_ble",
                    {
                      {"device", format_hex(btdevice)},
                      {"button", to_string(button_number)},
                      {"action", id(button_actions)[button_action].c_str()},
                      {"service", format_hex(uuid4u32)}
                    }
                  );
                  ESP_LOGI("Sonoff", "0x%x : %i : %s", btdevice, button_number, id(button_actions)[button_action].c_str());
                  if (button_number == 1 && button_action == 0) id(button1_short).publish_state(true);
                  else if (button_number == 1 && button_action == 1) id(button1_double).publish_state(true);
                  else if (button_number == 1 && button_action == 2) id(button1_long).publish_state(true);
                  else if (button_number == 2 && button_action == 0) id(button2_short).publish_state(true);
                  else if (button_number == 2 && button_action == 1) id(button2_double).publish_state(true);
                  else if (button_number == 2 && button_action == 2) id(button2_long).publish_state(true);
                  else if (button_number == 3 && button_action == 0) id(button3_short).publish_state(true);
                  else if (button_number == 3 && button_action == 1) id(button3_double).publish_state(true);
                  else if (button_number == 3 && button_action == 2) id(button3_long).publish_state(true);
                  else if (button_number == 4 && button_action == 0) id(button4_short).publish_state(true);
                  else if (button_number == 4 && button_action == 1) id(button4_double).publish_state(true);
                  else if (button_number == 4 && button_action == 2) id(button4_long).publish_state(true);
                  else if (button_number == 5 && button_action == 0) id(button5_short).publish_state(true);
                  else if (button_number == 5 && button_action == 1) id(button5_double).publish_state(true);
                  else if (button_number == 5 && button_action == 2) id(button5_long).publish_state(true);
                  else if (button_number == 6 && button_action == 0) id(button6_short).publish_state(true);
                  else if (button_number == 6 && button_action == 1) id(button6_double).publish_state(true);
                  else if (button_number == 6 && button_action == 2) id(button6_long).publish_state(true);
                  return;
                }
              }
            }

binary_sensor:
  - platform: template
    id: button1_short
    name: "Button 1 Short"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button1_short).publish_state(false);

  - platform: template
    id: button1_double
    name: "Button 1 Double"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button1_double).publish_state(false);

  - platform: template
    id: button1_long
    name: "Button 1 Long"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button1_long).publish_state(false);

  - platform: template
    id: button2_short
    name: "Button 2 Short"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button2_short).publish_state(false);

  - platform: template
    id: button2_double
    name: "Button 2 Double"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button2_double).publish_state(false);

  - platform: template
    id: button2_long
    name: "Button 2 Long"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button2_long).publish_state(false);

  - platform: template
    id: button3_short
    name: "Button 3 Short"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button3_short).publish_state(false);

  - platform: template
    id: button3_double
    name: "Button 3 Double"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button3_double).publish_state(false);

  - platform: template
    id: button3_long
    name: "Button 3 Long"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button3_long).publish_state(false);

  - platform: template
    id: button4_short
    name: "Button 4 Short"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button4_short).publish_state(false);

  - platform: template
    id: button4_double
    name: "Button 4 Double"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button4_double).publish_state(false);

  - platform: template
    id: button4_long
    name: "Button 4 Long"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button4_long).publish_state(false);

  - platform: template
    id: button5_short
    name: "Button 5 Short"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button5_short).publish_state(false);

  - platform: template
    id: button5_double
    name: "Button 5 Double"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button5_double).publish_state(false);

  - platform: template
    id: button5_long
    name: "Button 5 Long"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button5_long).publish_state(false);

  - platform: template
    id: button6_short
    name: "Button 6 Short"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button6_short).publish_state(false);

  - platform: template
    id: button6_double
    name: "Button 6 Double"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button6_double).publish_state(false);

  - platform: template
    id: button6_long
    name: "Button 6 Long"
    internal: false
    on_press:
      then:
        - delay: 1s
        - lambda: id(button6_long).publish_state(false);