myhomeiot / esphome-components

A collection of my ESPHome components
Other
257 stars 25 forks source link

Retry mechanisms for Home Assistant events? #31

Closed 3735943886 closed 1 month ago

3735943886 commented 1 month ago

In my setup, I use ESPHome to capture BLE advertisements and send them to Home Assistant using the homeassistant.event action. Occasionally, due to network instability or restarting Home Assistant, the event fails to send, leading to data loss. Having a retry mechanism would ensure that events are reliably sent to Home Assistant, improving the overall robustness of the system.

e.g.


ble_gateway:
  devices:
    - mac_address: 01:23:45:67:89:AB
    - mac_address: !secret lywsd03mmc_mac
  on_ble_advertise:
    then:
      homeassistant.event:
        event: esphome.on_ble_advertise
        data:
          packet: !lambda return packet;
        retry:
          attempts: 5
          delay: 500ms

Is it possible?

myhomeiot commented 1 month ago

Because limited resource of ESP I never seen any retry mechanism which talk to Home Assistant or MQTT. Usually BLE devices sends BLE advertisement very frequently so it's not a point to keep old packets. Also if you have several ESPHome BLE gateways located in the same area with high probability than a few of them receives advertisement and forward it to Home Assistant. If you have very important BLE devices and doesn't want to miss any data from them maybe better to parse data on ESP and export it to Home Assistant as a sensors like this.

Sure you can make the queue for packets, store them in ESPHome and send them when Home Assistant become available, below you can see the example. In ESPHome is the no way to understand if event send or not even for build-in service homeassistant.event. This example uses api_is_connected() call to check if it's has connection to Home Assistant. You can send saved packets using on_loop or interval. max_packet_queue_size the size of packet queue, you can make it bigger. If queue is full, it's delete older message and add new one. Hope this example will help you.

ble_gateway:
  devices:
    - mac_address: 01:23:45:67:89:AB
    - mac_address: !secret lywsd03mmc_mac
  on_ble_advertise:
    then:
      script.execute:
        id: packet_queue_push
        packet: !lambda return packet;

esphome:
  on_loop:
    then:
      - lambda: |-
          if (api_is_connected() && !id(packet_queue).empty()) {
            auto packet = id(packet_queue).back();
            id(packet_queue).pop_back();

            ESP_LOGE("packet_queue", "Fire event, packet: %s", packet.c_str());
            static auto capi = new esphome::api::CustomAPIDevice();
            capi->fire_homeassistant_event("esphome.on_ble_advertise", {{"packet", packet}});
          }

#interval:
#  - interval: 250ms
#    then:
#      - lambda: |-
#          if (api_is_connected() && !id(packet_queue).empty()) {
#            auto packet = id(packet_queue).back();
#            id(packet_queue).pop_back();
#            
#            ESP_LOGE("packet_queue", "Fire event, packet: %s", packet.c_str());
#            static auto capi = new esphome::api::CustomAPIDevice();
#            capi->fire_homeassistant_event("esphome.on_ble_advertise", {{"packet", packet}});
#          }

globals:
  - id: packet_queue
    type: std::vector<std::string>
    restore_value: no

script:
  - id: packet_queue_push
    mode: queued
    parameters:
      packet: string
    then:
      - lambda: |-
          static const size_t max_packet_queue_size = 5;

          id(packet_queue).insert(id(packet_queue).begin(), packet);
          if (id(packet_queue).size() > max_packet_queue_size)
            id(packet_queue).pop_back();

          ESP_LOGW("packet_queue", "Current queue size: %d", id(packet_queue).size());
          for (size_t i = 0; i < id(packet_queue).size(); i++)
            ESP_LOGD("packet_queue", "Queue[%d]: %s", i, id(packet_queue)[i].c_str());
3735943886 commented 1 month ago

Thank you very much. This was exactly what I needed.