Add support for sonoff s-mate and r5 (ewelink-remote sub-devices)

I recommend supporting the sonoff Bluetooth devices (s-mate and r5) to be integrated via Bluetooth that belongs to the Raspberry Pi or any local Home Assistant server. Also, this support should be added to esphome so we can add those Bluetooth (LE) devices to the sonoff devices that support ewelink remote control or any esp32 device.

They don’t seem to use the standard Bluetooth protocol, but a proprietary protocol called eWeLink-Remote

Yea, it’s a Bluetooth (LE).

is there any update on this topic?

I read on elsewhere something about s-mate switches are read-only sensors for home assistant via sonoff lan integration but I want to use it on automations as normal switch.

is this still not possible? (for s-mate2 switches)

Looks like there is thread on tasmota stating that it is BT LE-PHY - the only problem is to decode/understand switching info sent by scene controller. Maybe this will help to progress from hass standpoint - I assume a lot of people are waiting for some solution.

1 Like

Yeah, I am the one who mentioned that and I replied to you there

With the help of Tasmota discussion I was managed to make template sensors for S-Mate remote. The basic idea is to do the XOR operation with formerly captured encrypted packets that belong to a specific button. It works stable on a Doit ESP32 devkit v1 board. I have tested it with an S-Mate and its working fine so far.
ESPHome code snippet:

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

binary_sensor:

  - platform: template
    name: smate button1
    id: button_b1

  - platform: template
    name: smate button2
    id: button_b2

  - platform: template
    name: smate button3
    id: button_b3

switch:
  
  - platform: template
    name: store smate button1
    id: store_b1
    optimistic: True

  - platform: template
    name: store smate button2
    id: store_b2
    optimistic: True

  - platform: template
    name: store smate button3
    id: store_b3
    optimistic: True

globals:
   - id: btdata_store
     type: 'std::vector< esphome::esp32_ble::ESPBTUUID >'

   - id: btdata_b1
     type: 'std::vector< esphome::esp32_ble::ESPBTUUID >'
   - id: btdata_b1_serial
     type: 'uint32_t'
     initial_value: '0'
     restore_value: yes
   - id: btdata_b1_uuid4
     type: 'uint32_t'
     initial_value: '0'
     restore_value: yes

   - id: btdata_b2
     type: 'std::vector< esphome::esp32_ble::ESPBTUUID >'
   - id: btdata_b2_serial
     type: 'uint32_t'
     initial_value: '0'
     restore_value: yes
   - id: btdata_b2_uuid4
     type: 'uint32_t'
     initial_value: '0'
     restore_value: yes

   - id: btdata_b3
     type: 'std::vector< esphome::esp32_ble::ESPBTUUID >'
   - id: btdata_b3_serial
     type: 'uint32_t'
     initial_value: '0'
     restore_value: yes
   - id: btdata_b3_uuid4
     type: 'uint32_t'
     initial_value: '0'
     restore_value: yes

esp32_ble_tracker:
  scan_parameters:
    interval: 150ms
    window: 150ms
    active: false

  on_ble_advertise:
    - mac_address:
        - "66:55:44:33:22:11"
      then:
        - lambda: |-
            auto btdata = x.get_service_uuids();
            if (btdata[1].contains(0x78, 0xF6) && (btdata.size()==6)) {
              if (!(id(btdata_store) == btdata)) {
                id(btdata_store) = btdata;
                uint32_t btserial = (btdata[2].get_uuid().uuid.uuid32 & 0xff000000) | (btdata[3].get_uuid().uuid.uuid32 & 0x00ffffff);
                uint32_t uuid4u32 = btdata[4].get_uuid().uuid.uuid32;
                if (id(store_b1).state) {
                  id(btdata_b1) = btdata;
                  id(btdata_b1_serial) = btserial;
                  id(btdata_b1_uuid4) = uuid4u32;
                  id(store_b1).turn_off();
                } else if (id(store_b2).state) {
                  id(btdata_b2) = btdata;
                  id(btdata_b2_serial) = btserial;
                  id(btdata_b2_uuid4) = uuid4u32;
                  id(store_b2).turn_off();
                } else if (id(store_b3).state) {
                  id(btdata_b3) = btdata;
                  id(btdata_b3_serial) = btserial;
                  id(btdata_b3_uuid4) = uuid4u32;
                  id(store_b3).turn_off();
                } else {
                  if ((btserial==id(btdata_b1_serial)) && (((uuid4u32 >> 24) ^ (id(btdata_b1_uuid4) >> 24)) == ((uuid4u32 >> 16 & 0xff) ^ (id(btdata_b1_uuid4) >> 16 & 0xff))) && 
                     (((uuid4u32 >> 8 & 0xff) ^ (id(btdata_b1_uuid4) >> 8 & 0xff)) == ((uuid4u32 & 0xff) ^ (id(btdata_b1_uuid4) & 0xff)))) {
                    id(button_b1).publish_state(!(id(button_b1).state));
                    } else {
                    if ((btserial==id(btdata_b2_serial)) && (((uuid4u32 >> 24) ^ (id(btdata_b2_uuid4) >> 24)) == ((uuid4u32 >> 16 & 0xff) ^ (id(btdata_b2_uuid4) >> 16 & 0xff))) && 
                       (((uuid4u32 >> 8 & 0xff) ^ (id(btdata_b2_uuid4) >> 8 & 0xff)) == ((uuid4u32 & 0xff) ^ (id(btdata_b2_uuid4) & 0xff)))) {
                      id(button_b2).publish_state(!(id(button_b2).state));
                      } else {
                      if ((btserial==id(btdata_b3_serial)) && (((uuid4u32 >> 24) ^ (id(btdata_b3_uuid4) >> 24)) == ((uuid4u32 >> 16 & 0xff) ^ (id(btdata_b3_uuid4) >> 16 & 0xff))) && 
                           (((uuid4u32 >> 8 & 0xff) ^ (id(btdata_b3_uuid4) >> 8 & 0xff)) == ((uuid4u32 & 0xff) ^ (id(btdata_b3_uuid4) & 0xff)))) {
                          id(button_b3).publish_state(!(id(button_b3).state));
                        }
                      }
                    }
                  }
                }
              }

At first You need to teech each S-mate buttons with the help of the “store” switches one by one. As soon as it receives an S-mate sending, the sotre switch goes off, and the corresponding binary template sensor will receive the further S-mate commands.

1 Like

Would this work with s- mate 2?

If it uses the same protocol it would work. I’ll order an s-mate 2 soon and report the result here.

1 Like

Hi, just new to HA and my first ever post. changed all my devices over from WiFi minir4 to zigbee minir2, but unfortunately have to keep the sonoff minir4 through sonoff lan integration as using the smart 2 with a dumb rocker switch. Has there been any update on how an automation/scene can work in HA? Currently my smate2 through sonoff lan shows no entities for trigger to create an automation. Was wondering if there has been any head way in that department? Many Thanks in advance

S-mate 2 works fine also. I just modified the code in my 1st post to check the serial num also and made some refactoring.

1 Like

Hi. Thanks @norbim1 your lambda code for the s-mate really help here with a R5.
I use the same logic but extended it operate with 3 different R5 and implemented the different 18 possible buttons. The behaviour is similar to what Sonoff add-on does in HA.
Still needs some polishing with the clear of the text_sensor, but for a PoC it worked perfectly.

text_sensor:
  - platform: template
    name: "Device 1 Button Status"
    id: button_text_sensor_0
    icon: "mdi:gesture-tap-button"
  - platform: template
    name: "Device 2 Button Status"
    id: button_text_sensor_1
    icon: "mdi:gesture-tap-button"
  - platform: template
    name: "Device 3 Button Status"
    id: button_text_sensor_2
    icon: "mdi:gesture-tap-button"

switch:
  - platform: template
    name: Store R5 id1
    id: store_id1
    optimistic: True
  - platform: template
    name: Store R5 id2
    id: store_id2
    optimistic: True
  - platform: template
    name: Store R5 id3
    id: store_id3
    optimistic: True

binary_sensor:
  - platform: template
    name: R5 id1
    id: button_b1
  - platform: template
    name: R5 id2
    id: button_b2
  - platform: template
    name: R5 id3
    id: button_b3




globals:
  - id: btdata_store
    type: 'std::vector< esphome::esp32_ble::ESPBTUUID >'
  - id: btdata_r5_id
    type: std::vector<unsigned int>
    initial_value: "std::vector<unsigned int>{}"
  - id: btdata_r5_id1
    type: 'uint32_t'
    initial_value: '0'
    restore_value: yes
  - id: btdata_r5_id2
    type: 'uint32_t'
    initial_value: '0'
    restore_value: yes
  - id: btdata_r5_id3
    type: 'uint32_t'
    initial_value: '0'
    restore_value: yes
  - id: btdata_uuid4_str
    type: std::string
    restore_value: yes
    #button press UUID by order. The order may vary according to device version
    initial_value: '"0xFA1B99AA, 0x9575F6C5, 0x9F7CFCCF, 0x29C84B79, 0xBF5FDDEF, 0xA043C2F0, 0xBF5EDAEF, 0xA141C4F1, 0x9D7EF8CD, 0x8A6BECDA, 0xD535B385, 0x1FFC794F, 0xE60781B6, 0x2FCF487F, 0xD734B087, 0x02E36652, 0xEB0B8FBB, 0x32D15662"'

esp32_ble_tracker:
  scan_parameters:
    interval: 150ms
    window: 150ms
    active: false

  on_ble_advertise:
    - mac_address: "66:55:44:33:22:11"
      then:
        - lambda: |-
            auto btdata = x.get_service_uuids();
            if (id(btdata_r5_id).size() < 3) {
              id(btdata_r5_id).resize(3);  
            }

            if (btdata.size() >= 6 && btdata[1].contains(0x78, 0xF6)) {
              if (!(id(btdata_store) == btdata)) {
                id(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;
                if (id(store_id1).state) {
                  id(btdata_r5_id)[0] = btdevice;
                  id(store_id1).turn_off();
                } else if (id(store_id2).state) {
                  id(btdata_r5_id)[1] = btdevice;
                  id(store_id2).turn_off();
                } else if (id(store_id3).state) {
                  id(btdata_r5_id)[2] = btdevice;
                  id(store_id3).turn_off();
                } else {
                  std::vector<uint32_t> parsed_values;
                  std::string btdata_str = id(btdata_uuid4_str);
                  size_t pos = 0;
                  
                  while ((pos = btdata_str.find(",")) != std::string::npos) {
                    std::string token = btdata_str.substr(0, pos);
                    parsed_values.push_back(strtoul(token.c_str(), nullptr, 16));
                    btdata_str.erase(0, pos + 1);
                  }
                  parsed_values.push_back(strtoul(btdata_str.c_str(), nullptr, 16)); // Last value

                  for (size_t j = 0; j < id(btdata_r5_id).size(); j++) {
                    if (btdevice == id(btdata_r5_id)[j]) {
                      for (size_t i = 0; i < parsed_values.size(); i++) {
                        uint32_t stored_value = parsed_values[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 
                              
                              std::string state = "Button_" + std::to_string(button_number) + ":";
                            
                              //determinie the type of press
                              if (i % 3 == 0) {
                                state += "Single_Press";
                              } else if (i % 3 == 1) {
                                state += "Double_Press";
                              } else if (i % 3 == 2) {
                                state += "Long_Press";
                              }
                              if (j == 0) {
                                id(button_text_sensor_0).publish_state(state);
                              } else if (j == 1) {
                                id(button_text_sensor_1).publish_state(state);
                              } else if (j == 2) {
                                id(button_text_sensor_2).publish_state(state);
                              }

                              //this part is really ugly. next setps is to place it outside de lambda
                              delay(500); 
                              
                              if (j == 0) {
                                id(button_text_sensor_0).publish_state("");
                              } else if (j == 1) {
                                id(button_text_sensor_1).publish_state("");
                              } else if (j == 2) {
                                id(button_text_sensor_2).publish_state("");
                              }
                              return;
                        }
                      }
                    }
                  }
                }
              }
            }

3 Likes

I would like to test this with my S-Mate 2. I already have an ESP board as a bluetooth proxy. Can I add this code without losing previous functionality?

Here little bit shorter code for Sonoff R5/S-Mate/S-Mate2.
ESPHome will fireHA event with data about device id, button pressed and kind of action.
Events can be seen in:

  • Developer Tools → Events → Event to subscribe to: esphome.sonoff_ble → Start Listening.
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: false

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

            if (btdata.size() >= 6 && btdata[1].contains(0x78, 0xF6)) {
              if (!(btdata_store == btdata)) {
                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);

                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()}});
                    ESP_LOGI("Sonoff", "0x%x : %i : %s", btdevice, button_number,id(button_actions)[button_action].c_str());
                    return;
                  }
                }
              }
            }

binary_sensor:
  - 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:

Additionally possible to collect list of BLE MAC’s data received from:

globals:
  - id: btdata_id_str
    type: std::vector<std::string>
    restore_value: no
    initial_value: "std::vector<std::string>{}"

switch:
  - platform: template
    name: "BLE List Collect"
    id: ble_list_collect
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true

button:
  - platform: template
    name: "BLE List Publish"
    on_press:
      - script.execute: ble_list_publish

script:
  - id: ble_list_publish
    then:
      - lambda: |-
          if (id(ble_list_collect).state) {
            ESP_LOGI("BLE", "Address List (%02i)", id(btdata_id_str).size());
            for (auto s: id(btdata_id_str)) {
              ESP_LOGI("BLE", "%s", s.c_str());
            }
          } else {
            ESP_LOGI("BLE", "Address List disabled");
          }


esp32_ble_tracker:
  on_ble_advertise:
    - then:
        - lambda: |-
            if (id(ble_list_collect).state && (x.get_address_type() != BLE_ADDR_TYPE_RANDOM)) {
              std::string address_str = x.address_str();
              for (auto s: id(btdata_id_str)) {
                if (address_str == s) return;
              }
              id(btdata_id_str).push_back(address_str);
              id(ble_list_publish_script).execute();
            }

If want to see all MAC’s in the air - remove (x.get_address_type() != BLE_ADDR_TYPE_RANDOM) condition.

Here is an example of automation:

  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
1 Like

will this also work with a S-Mate?

Not right now. Will check soon for S-Mate & S-Mate2.

2 Likes

An finally by reordering codes in array that way:

    initial_value: '{0x8A6BECDA, 0xD535B385, 0x1FFC794F, 0xE60781B6, 0x2FCF487F, 0xD734B087, 0x02E36652, 0xEB0B8FBB, 0x32D15662,
                     0xFA1B99AA, 0x9575F6C5, 0x9F7CFCCF, 0x29C84B79, 0xBF5FDDEF, 0xA043C2F0, 0xBF5EDAEF, 0xA141C4F1, 0x9D7EF8CD}'

we geting nice numbering.

For S-Mate & S-Mate now button numbers are 1-2-3, for R5:

1 2 3
6 5 4

S-Mate2:
When DIP switch in position “Momentary switch” - detects “short/double/long” events.
In position “Rocker” switch - generates same event “short” on both transitions open-close & close-open.

S-Mate:
In DIP Switch position “Rocker” switch - generates same event “short” on both transitions open-close & close-open.
In position “Pulse” - only generates “short” on transition open-close contact, nothing more.

2 Likes

Looking for a little help with this. I have 3 Smate-2s and have the esp32 showing the button presses and the list of macs e.g. [D][Sonoff_R5:148]: 5a2bcfd9:4:short which is button 1 on the first smate. However nothing seems to show up in Home assistant.

In particular, I presume I need to fill the initial value array with device specific numbers but it is not clear how I find these numbers? Any help appreciated.

Please check post 14 with updated ESPHome yaml, how to see fired events & automation example.

Thanks very much - got all three of the smate2s working.

One further question - if I had a couple of esp32s running this code and both of them picked up the ble broadcast, would this result in multiple events?

1 Like