I have a grage door.

It has an opener.

I am slightly too lazy to wire up the external controls of my garage door opener.

But it does have an app, and the app shows the state of the door opening and closing.

Using the esp32_ble_tracker and some debugging ESP_LOGDs, we can see:

[19:25:28][D][ble_adv:060]: Advertised manufacturer data:
[19:25:36][D][ble_adv:062]:   - 02026d200000: (length 6)
[19:25:36][D][ble_adv:062]:   - 13c000000000000000e489feb375b43104: (length 17)

This is actually a single long advert sequence (that I forgot to keep an example of) that is type-length decoded to these two separate manufacturing messages. The manufacturing ID is set to 0x07B4 for both.

Checking over time, we can see something like the following when the garage door is opening:

[19:24:55][D][ble_adv:062]:   - 149200000000000000e489feb375b43104: (length 17)
[19:24:55][D][ble_adv:062]:   - 149200000000000000e489feb375b43104: (length 17)
[19:24:55][D][ble_adv:062]:   - 149400000000000000e489feb375b43104: (length 17)
[19:24:55][D][ble_adv:062]:   - 149400000000000000e489feb375b43104: (length 17)
[19:24:55][D][ble_adv:062]:   - 149600000000000000e489feb375b43104: (length 17)
[19:24:56][D][ble_adv:062]:   - 149800000000000000e489feb375b43104: (length 17)
[19:24:56][D][ble_adv:062]:   - 149a00000000000000e489feb375b43104: (length 17)
[19:24:56][D][ble_adv:062]:   - 149c00000000000000e489feb375b43104: (length 17)
[19:24:56][D][ble_adv:062]:   - 149e00000000000000e489feb375b43104: (length 17)

This confirms research on other Hörmann openers, this is double the percentage open of the door, between 0–C8.

[19:25:02][D][ble_adv:062]:   - 02026d200000: (length 6)
[19:25:03][D][ble_adv:062]:   - 02022d200000: (length 6)

In the other message, the third byte flips from 0x2 to 0x6 when in operation. With that, we can write the following[^mfr_advert] in ESPhome:


sensor:
  - platform: template
    name: Garage door percentage
    entity_category: diagnostic
    id: door_percent
    accuracy_decimals: 0
    unit_of_measurement: '%'
    filters:
      - or:
        - throttle: 60s
        - delta: 1.0

binary_sensor:
  - platform: template
    id: door_moving
    name: Garage door moving
    publish_initial_state: true
    device_class: moving

esp32_ble_tracker:
  on_ble_advertise:
    - mac_address: E5:64:xx:xx:xx:xx
      then:
        - lambda: |-
            auto mfrs = x.get_manufacturer_datas();
            if (mfrs.size() == 2) {
              bool moving = mfrs[0].data[2] & 0x40;
              int percent = mfrs[1].data[1] / 2;

              id(door_percent).publish_state(percent);
              id(door_moving).publish_state(moving);
            }

And then in Home Assistant, a helper can convert the door percentage open to a binary sensor:

{{ states('sensor.garage_door_percentage') | int > 0 }}

This could obviously be done directly by ESPhome, and the only reason it isn’t is because I wanted to test the decoding. I don’t use the percentage or the moving variable, although the latter is tempting for some automation in the summer…

Notes

There is a on_ble_manufacturer_data_advertise Trigger in ESPhome, but I either used it wrongly or it doesn’t expect two identical mfr IDs in the same beacon, as I only saw the first message. The generic BLE advert lambda argument is just as easy to work with.

There’s a filter on the door percentage filter as this advert is sent at 5Hz! That’s quite a lot of data to tell us very little! ESPhome supports generic filtering of sensors, and this combo is straight from the docs “Update once a minute or when it changes”.