wifwucite / esphome-ble-controller

Bluetooth Low Energy (BLE) controller for ESPHome
MIT License
102 stars 18 forks source link

This component provides a Bluetooth Low Energy (BLE) controller for ESPHome. It allows to monitor sensor data and control switches and other components via BLE connections (for example from a smart phone):

BLE connection from phone

In addition, there is a command channel, which allows to configure the WiFi credentials for the ESP32 over BLE (among other things).

⚠️ Note: This controller only works with ESP32 micro-controllers, not with ESP8266 chips because they do not offer built-in BLE support.

Installation

This component is compatible with ESPHome 2022.12.0 or later. (For earlier ESPHome releases, please try an earlier version of this component.) To install this component you do not need to download or copy anything, you can just refer to this external component from the yaml file as shown below.

Configuration

Getting started

The following configuration shows how to make a (template) switch accessible via BLE:

external_components:
  - source: github://wifwucite/esphome-ble-controller

switch:
  - platform: template
    name: "Template Switch"
    optimistic: true
    id: template_switch

esp32_ble_controller:
  services:
  - service: "4fafc201-1fb5-459e-8fcc-c5c9c331915d"
    characteristics:
      - characteristic: "beb5483e-36e1-4688-b7f5-ea07361b26ab"
        exposes: template_switch

You define your sensors, switches and other compoments as usual (like the template switch in the example). Make sure to assign an id to each component you want to expose via bluetooth. Then you add esp32_ble_controller to include the controller itself. In order to make a component available you need to define a corresponding BLE characteristic that is contained in a BLE service. If you are not familiar with BLE you do need to worry much. For each characteristic and each service you simply need a different UUID, which you could generate here. A service is basically used for grouping characteristics, so it can contain multiple characteristics. Each characteristic exposes a component, which is configured via the exposes property specifying the id of the respective component.

If you flash this example configuration and connect to your ESP32 device from your phone or tablet, you can see device information similar to the data displayed in the image above. Note how the service UUID and characteristic UUID provided in the characteristic configuration of the template switch now show up. Besides the switch that was configured explicitly there is also a so-called maintenance service which is provided by the controller automatically. It allows you to send commands to your device and access some logging related characteristics, which will be explained below.

Configuration options

esp32_ble_controller:
  services:
  - service: <service 1 UUID>
    characteristics:
      - characteristic: <characteristic 1.1 UUID>
        exposes: <id of component>
      - characteristic: <characteristic 1.2 UUID>
        exposes: <id of component>
  - service: <service 2 UUID>
    characteristics:
      - characteristic: <characteristic 2.1 UUID>
        exposes: <id of component>

  # you can add your own custom commands
  # The description is shown when the user sends "help test-cmd" as command.
  commands:
  - command: test-cmd
    description: just a test
    on_execute:
    - logger.log: "test command executed"

  # allows to enable or disable security, default is 'secure'
  # Options:
  # - none: 
  #     disables security
  # - bond:
  #     no real security, but devices do some bonding (pairing) upon first connect
  # - secure:
  #     enables secure connections and man-in-the-middle protection
  # If the "on_show_pass_key" automation is present, then upon first pairing the other device (your phone) 
  # sends a 6-digit pass key to the ESP and the ESP is supposed to display it so that it can be entered on the other device.
  # This automation is not available for the "none" mode, optional for the "bond" mode, and required for the "secure" mode.
  security_mode: secure

  # allows to disable the maintenance service, default is 'true'
  # When 'false', the maintenance service is not exposed, which provides at least some protection when security mode is "none".
  # Note: Writeable characteristics like those for switches or fans may still be written by basically anyone.
  maintenance: true

  # automation that is invoked when the pass key should be displayed, the pass key is available in the automation as "pass_key" variable of type std::string (not available if security mode is "none")
  # the example below just logs the pass keys
  on_show_pass_key:
  - logger.log:
      format: "pass key is %s"
      args: 'pass_key.c_str()'
  # automation that is invoked when the authentication is complete, the boolean "success" indicates success or failure (not available if security mode is "none")
  on_authentication_complete:
  - logger.log:
      format: "BLE authentication complete %d" # shows 1 on success, 0 on failure
      args: 'success'
  # automations that are invoked when the device is connected to / disconnected from a client (phone or tablet for example)
  on_connected:
  - logger.log: "I am connected. :-)"
  on_disconnected:
  - logger.log: "I am disconnected. :-("

Features

BLE security

By default security is switched on, which means that the ESP32 has to be bonded (paired) when it is used for the first time with a new device. (This feature can be switched off via configuration.) Secure connections and protection against man-in-the-middle attacks are enabled. The device to be bonded sends a pass key (a 6 digit PIN) to the ESP32. Via the on_show_pass_key automation you can log the pass key or even show it on a display. (At the bottom you can find an example that makes use of a display to show the pass key until pairing is complete.)

Note: On some computers (like the MacBook Pro for example) the very first bonding process seems to fail if the security is enabled. In that case you can change the security mode to "bond" for the very first encounter (without an on_show_pass_key automation). After that succeeded you may change the mode back to "secure". Even if you delete the bonding information from both devices later on, secure bonding attempts will work and recreate the bonding.

Maintenance service

The maintenance BLE service is provided implicitly when you include esp32_ble_controller in your yaml configuration unless you disable it explicitly via the maintenance property. It provides two characteristics:

Custom commands

A custom commmand consists of three parts: name, description (shown by help) and the on_execute automation that is executed when the command runs. A custom command can have arguments which are passed to the automation as a vector of strings named arguments. In addition a custom command send a result, which can be defined by assigning a string to the result argument or via the ble_cmd.send_result automation (similar to logger.log). Both variants are shown below.

esp32_ble_controller:
  commands:
  - command: test-cmd
    description: just a test
    on_execute:
    - if:
        condition:
          lambda: 'return arguments.empty();'
        then:
          - lambda: |-
              result = "test command executed without arguments";
        else:
          - ble_cmd.send_result:
              format: "test command executed with argument %s"
              args: 'arguments[0].c_str()'

Supported components

Examples

Show pass key on display during authentication

Configuration to show the 6-digit pass key during authentication on a display: BLE pass key on display

display:
  - platform: ...
    ...
    pages:
      - id: page_standard
        lambda: |-
          // print standard stuff
      - id: page_ble_pass_key
        lambda: |-
          it.print(0, 0, id(my_font), TextAlign::TOP_LEFT, "Bluetooth");
          it.print(0, 20, id(my_font), TextAlign::TOP_LEFT, "Key");
          it.print(0, 40, id(my_font), TextAlign::TOP_LEFT, id(ble_pass_key).c_str());

globals:
  - id: ble_pass_key
    type: std::string

esp32_ble_controller:
  services:
  - service: "4fafc201-1fb5-459e-8fcc-c5c9c331915d"
    characteristics:
      - characteristic: "beb5483e-36e1-4688-b7f5-ea07361b26ab"
        exposes: template_switch
  on_show_pass_key:
    then:
      - lambda: |-
          id(ble_pass_key) = pass_key;
      - display.page.show: page_ble_pass_key
      - component.update: my_display
  on_authentication_complete:
    then:
      - lambda: |-
          id(ble_pass_key) = "";
      - display.page.show: page_standard
      - component.update: my_display

Integration with ble_client (light and switch example)

This example shows how to integrate with ble_client (a standard component of ESPHome). In the example we toggle a light with a momentary swith. Why do we need bluetooth? Because in our case the switch and the light are connected to two different ESP32 boards, and we use BLE to "tunnel" the switch press event from one ESP32 (the BLE client) to the other ESP32 (the server).

So, our setup looks like this:

The example has been provided evlo. Pins in example are based on M5stack AtomS3 devices.

Switch configuration (BLE client)

⚠️ Note: BLE MAC address can be different than WiFi MAC address, make sure you are using correct one.

substitutions:
  hostname: demo-switch-ble
  device_id: demo_switch_ble
  comment: ESPHome ble demo switch
  # You have to put your server's BLE address here, see server's startup log
  mac_light: "f4:12:fa:61:00:f5"
  switch_button_id: button0
  light_peer_id: demo_light
  light_virtual_switch_uuid: 32f40d3a-d24d-11ed-afa1-0242ac120002
  light_control_service_uuid: 2dc01d40-d24d-11ed-afa1-0242ac120002

ble_client:
  - mac_address: ${mac_light}
    id: ${light_peer_id}_client
    on_connect:
      then:
        - lambda: |-
            ESP_LOGD("ble_client", "Connected to ${light_peer_id}");

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO41
    id: ${switch_button_id}
    name: ${switch_button_id}
    icon: "mdi:gesture-tap-button"
    on_release:
      then:
        - logger.log: "Toggling the light using BLE"
        - ble_client.ble_write:
            id: ${light_peer_id}_client
            service_uuid: ${light_control_service_uuid}
            characteristic_uuid: ${light_virtual_switch_uuid}
            value: 1

Light configuration (BLE server)

substitutions:
  hostname: demo-light-ble
  device_id: demo_light_ble
  mac_light: "f4:12:fa:61:00:f5"
  light_virtual_switch_uuid: 32f40d3a-d24d-11ed-afa1-0242ac120002
  light_control_service_uuid: 2dc01d40-d24d-11ed-afa1-0242ac120002
  light_toggle_command: demo-toggle
  light_virtual_switch_id: light_virtual_switch

esp32_ble_controller:
  security_mode: none
  services:
    - service: ${light_control_service_uuid}
      characteristics:
        - characteristic: ${light_virtual_switch_uuid}
          exposes: ${light_virtual_switch_id}
  # The commands and the events below are for debugging purposes.
  commands:
    - command: ${light_toggle_command}
      description: Toggle the light demo
      on_execute:
        - logger.log: "Toggle executed"
        - light.toggle: status_led
  on_connected:
    - logger.log: "Connected."
  on_disconnected:
    - logger.log: "Disconnected."

switch:
  - platform: template
    id: ${light_virtual_switch_id}
    turn_on_action:
      - logger.log: "Switch ON"
      - light.toggle: status_led
    turn_off_action:
      - logger.log: "Switch OFF"
      - light.toggle: status_led

light: 
  - platform: neopixelbus
    num_leds: 1
    variant: WS2812
    pin: GPIO35
    id: status_led
    name: ${hostname} RGB LED
    default_transition_length: 
      milliseconds: 0