├── .gitignore ├── BLE-PassKey.png ├── BLE-Services-and-Characteristics.png ├── LICENSE ├── README.md └── components └── esp32_ble_controller ├── __init__.py ├── automation.h ├── ble_command.cpp ├── ble_command.h ├── ble_component_handler.h ├── ble_component_handler_base.cpp ├── ble_component_handler_base.h ├── ble_component_handler_factory.cpp ├── ble_component_handler_factory.h ├── ble_fan_handler.cpp ├── ble_fan_handler.h ├── ble_maintenance_handler.cpp ├── ble_maintenance_handler.h ├── ble_sensor_handler.cpp ├── ble_sensor_handler.h ├── ble_switch_handler.cpp ├── ble_switch_handler.h ├── ble_utils.cpp ├── ble_utils.h ├── esp32_ble_controller.cpp ├── esp32_ble_controller.h ├── thread_safe_bounded_queue.h ├── wifi_configuration_handler.cpp └── wifi_configuration_handler.h /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | **/__pycache__ -------------------------------------------------------------------------------- /BLE-PassKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wifwucite/esphome-ble-controller/ca491f1ed0b31479cd4b8917ad9c34e2f6c9bfe5/BLE-PassKey.png -------------------------------------------------------------------------------- /BLE-Services-and-Characteristics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wifwucite/esphome-ble-controller/ca491f1ed0b31479cd4b8917ad9c34e2f6c9bfe5/BLE-Services-and-Characteristics.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ESP32 BLE Controller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This component provides a Bluetooth Low Energy (BLE) controller for [ESPHome](https://esphome.io). It allows to monitor sensor data and control switches and other components via BLE connections (for example from a smart phone): 2 | 3 | ![BLE connection from phone](BLE-Services-and-Characteristics.png) 4 | 5 | In addition, there is a command channel, which allows to configure the WiFi credentials for the ESP32 over BLE (among other things). 6 | 7 | ⚠️ **Note**: This controller only works with ESP32 micro-controllers, not with ESP8266 chips because they do not offer built-in BLE support. 8 | 9 | ## Installation 10 | 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. 11 | 12 | ## Configuration 13 | 14 | ### Getting started 15 | 16 | The following configuration shows how to make a (template) switch accessible via BLE: 17 | ```yaml 18 | external_components: 19 | - source: github://wifwucite/esphome-ble-controller 20 | 21 | switch: 22 | - platform: template 23 | name: "Template Switch" 24 | optimistic: true 25 | id: template_switch 26 | 27 | esp32_ble_controller: 28 | services: 29 | - service: "4fafc201-1fb5-459e-8fcc-c5c9c331915d" 30 | characteristics: 31 | - characteristic: "beb5483e-36e1-4688-b7f5-ea07361b26ab" 32 | exposes: template_switch 33 | ``` 34 | 35 | You define your sensors, switches and other compoments as usual (like the [template switch](https://esphome.io/components/switch/template.html) in the example). 36 | Make sure to assign an id to each component you want to expose via bluetooth. 37 | Then you add `esp32_ble_controller` to include the controller itself. 38 | 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](https://www.uuidgenerator.net). 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. 39 | 40 | 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. 41 | 42 | ### Configuration options 43 | 44 | ```yaml 45 | esp32_ble_controller: 46 | services: 47 | - service: 48 | characteristics: 49 | - characteristic: 50 | exposes: 51 | - characteristic: 52 | exposes: 53 | - service: 54 | characteristics: 55 | - characteristic: 56 | exposes: 57 | 58 | # you can add your own custom commands 59 | # The description is shown when the user sends "help test-cmd" as command. 60 | commands: 61 | - command: test-cmd 62 | description: just a test 63 | on_execute: 64 | - logger.log: "test command executed" 65 | 66 | # allows to enable or disable security, default is 'secure' 67 | # Options: 68 | # - none: 69 | # disables security 70 | # - bond: 71 | # no real security, but devices do some bonding (pairing) upon first connect 72 | # - secure: 73 | # enables secure connections and man-in-the-middle protection 74 | # If the "on_show_pass_key" automation is present, then upon first pairing the other device (your phone) 75 | # 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. 76 | # This automation is not available for the "none" mode, optional for the "bond" mode, and required for the "secure" mode. 77 | security_mode: secure 78 | 79 | # allows to disable the maintenance service, default is 'true' 80 | # When 'false', the maintenance service is not exposed, which provides at least some protection when security mode is "none". 81 | # Note: Writeable characteristics like those for switches or fans may still be written by basically anyone. 82 | maintenance: true 83 | 84 | # 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") 85 | # the example below just logs the pass keys 86 | on_show_pass_key: 87 | - logger.log: 88 | format: "pass key is %s" 89 | args: 'pass_key.c_str()' 90 | # automation that is invoked when the authentication is complete, the boolean "success" indicates success or failure (not available if security mode is "none") 91 | on_authentication_complete: 92 | - logger.log: 93 | format: "BLE authentication complete %d" # shows 1 on success, 0 on failure 94 | args: 'success' 95 | # automations that are invoked when the device is connected to / disconnected from a client (phone or tablet for example) 96 | on_connected: 97 | - logger.log: "I am connected. :-)" 98 | on_disconnected: 99 | - logger.log: "I am disconnected. :-(" 100 | ``` 101 | 102 | ## Features 103 | 104 | ### BLE security 105 | 106 | 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.) 107 | 108 | 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. 109 | 110 | ### Maintenance service 111 | 112 | 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: 113 | 114 | * Command channel (UTF-8 string, read-write): 115 | Allows to send commands to the ESP32 and receives answers back from it. A command is a string which consists of the name of the command and (possibly) arguments, separated by spaces. 116 | You can define your own custom commands in yaml as described below in detail. 117 | There are also some built-in commands, which are always available: 118 | * help [<command>]: 119 | Without argument, it lists all available commands. When the name of a command is given like in "help log-level" it displays a specific description for this command. 120 | * ble-maintenance [off]: 121 | Switches the maintenance service off and boots the device. After the boot the maintenance service will **not be availble anymore until you flash your device again**. Thus you can set up your device with the maintenance service enabled and disable that service as soon as everything is running (if you are operating your device in an insecure mode). 122 | * ble-services [on|off]: 123 | Switches the component related (non-maintenance) BLE services on or off and boots the device. You may wonder why one should switch off these services. On most ESP32 boards both BLE and WiFi share the same physical 2,4 GHz antenna on the ESP32. So, too much traffic on both of them can cause it to crash and reboot. Short-lived WiFi connections for sending MQTT messages work fine with services enabled. However, when connecting to the [web server](https://esphome.io/components/web_server.html) or for [OTA updates](https://esphome.io/components/ota.html) services should be disabled. (Note that ESPHome permits configurations without the WiFi component, so if you encounter problems with BLE you could try disabling WiFi completely.) 124 | * wifi-config <ssid> <password> [hidden]: 125 | Sets the SSID and the password to use for connecting to WiFi. The optional 'hidden' argument marks the network as hidden network. It is recommended to use this command only when security is enabled. You can also use "wifi-config clear" to clear the WiFi configuration; then the default credentials (compiled into the firmware) will be used. (This command is only available if the WiFi component has been configured at all.) 126 | * parings [clear]: 127 | Lists the addresses of all paired devices, or clears all paired devices. 128 | * version: 129 | Shows the version of the device. (Currently this displays the compilation time.) 130 | * log-level [level]: 131 | If no argument is provided, it queries the current log level for logging over BLE. When a level argument is provided like in "log-level 0" the log level is adjusted. Currently the levels have to be specified as integer number between 0 (= no logging) and 7 (= very verbose). 132 | ⚠️ **Note**: You cannot get finer logging than the overall log level specified for the [logger component](https://esphome.io/components/logger.html). 133 | * Log messages (UTF-8 string, read-only): 134 | Provides the latest log message that matches the configured log level. 135 | 136 | #### Custom commands 137 | 138 | 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`](https://esphome.io/components/logger.html)). Both variants are shown below. 139 | 140 | ```yaml 141 | esp32_ble_controller: 142 | commands: 143 | - command: test-cmd 144 | description: just a test 145 | on_execute: 146 | - if: 147 | condition: 148 | lambda: 'return arguments.empty();' 149 | then: 150 | - lambda: |- 151 | result = "test command executed without arguments"; 152 | else: 153 | - ble_cmd.send_result: 154 | format: "test command executed with argument %s" 155 | args: 'arguments[0].c_str()' 156 | ``` 157 | 158 | ### Supported components 159 | 160 | * [Binary sensor](https://esphome.io/components/binary_sensor/index.html) (read-only, 2-byte unsigned little-endian integer): The characteristic stores the boolean sensor value as integer (0 or 1). 161 | * [Sensor](https://esphome.io/components/sensor/index.html) (read-only, 4-byte little-endian float): The characteristic stores the floating point sensor value (without unit). 162 | * [Text sensor](https://esphome.io/components/text_sensor/index.html) (read-only, UTF-8 string): The characteristic stores the string sensor value. 163 | * [Switch](https://esphome.io/components/switch/index.html) (read-write, 2-byte unsigned little-endian integer): The characteristic represents the on-off state of the switch as integer value (0 or 1). Writing a 0 or 1 can be used to turn the switch on or off. 164 | * [Fan](https://esphome.io/components/fan/index.html) (read-write, UTF-8 string): The characteristic represents the complete state of the fan (not only on-off, also speed, oscillating, and direction). Writing a string option can be used to change the on-off state ("on"/"off"), the speed (an integer value), the oscillating flag ("yes"/"no"), or the direction ("forward"/"reverse"). You can set more than one option at a time: "on 45 no" would turn the fan on set its speed to 45 and switch oscillation off. 165 | 166 | # Examples 167 | 168 | ## Show pass key on display during authentication 169 | 170 | Configuration to show the 6-digit pass key during authentication on a display: 171 | ![BLE pass key on display](BLE-PassKey.png) 172 | 173 | ```yaml 174 | display: 175 | - platform: ... 176 | ... 177 | pages: 178 | - id: page_standard 179 | lambda: |- 180 | // print standard stuff 181 | - id: page_ble_pass_key 182 | lambda: |- 183 | it.print(0, 0, id(my_font), TextAlign::TOP_LEFT, "Bluetooth"); 184 | it.print(0, 20, id(my_font), TextAlign::TOP_LEFT, "Key"); 185 | it.print(0, 40, id(my_font), TextAlign::TOP_LEFT, id(ble_pass_key).c_str()); 186 | 187 | globals: 188 | - id: ble_pass_key 189 | type: std::string 190 | 191 | esp32_ble_controller: 192 | services: 193 | - service: "4fafc201-1fb5-459e-8fcc-c5c9c331915d" 194 | characteristics: 195 | - characteristic: "beb5483e-36e1-4688-b7f5-ea07361b26ab" 196 | exposes: template_switch 197 | on_show_pass_key: 198 | then: 199 | - lambda: |- 200 | id(ble_pass_key) = pass_key; 201 | - display.page.show: page_ble_pass_key 202 | - component.update: my_display 203 | on_authentication_complete: 204 | then: 205 | - lambda: |- 206 | id(ble_pass_key) = ""; 207 | - display.page.show: page_standard 208 | - component.update: my_display 209 | ``` 210 | 211 | ## Integration with ble_client (light and switch example) 212 | 213 | This example shows how to integrate with [ble_client](https://esphome.io/components/ble_client.html) (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). 214 | 215 | So, our setup looks like this: 216 | * We have a physical momentary switch connected to a GPIO pin of our first ESP32 board, which will be the BLE client. The physical light is connected to a GPIO pin of our second ESP32 board, which will be the BLE server. 217 | * On the BLE client, we use a binary sensor compoment to detect when the momentary switch is pressed by a human. We use the [ble_client](https://esphome.io/components/ble_client.html) component to send a value from the client to the server when the switch is pressed. 218 | * On BLE server, we expose a template switch via this BLE controller component as a characteristic. The BLE client writes to this charactericstic and thus informs the server about button press. The template switch on the server controls the actual light compoment, which turns the physical light on or off. 219 | * To summarize, the sequence of events is this: 220 | * Human presses button of momentary switch connected to the BLE client board. 221 | * Binary sensor component detects change of GPIO level and writes to a characteristic on the BLE server (using ``ble_client.ble_write``). 222 | * The BLE controller component on the server observes this write and toggles the template switch. 223 | * Template switch toggles the light component. 224 | * Light component sets the GPIO level on the server side, which controls the physical light. 225 | 226 | The example has been provided [evlo](https://github.com/evlo). Pins in example are based on M5stack AtomS3 devices. 227 | 228 | ### Switch configuration (BLE client) 229 | 230 | ⚠️ **Note**: BLE MAC address can be different than WiFi MAC address, make sure you are using correct one. 231 | 232 | ```yaml 233 | substitutions: 234 | hostname: demo-switch-ble 235 | device_id: demo_switch_ble 236 | comment: ESPHome ble demo switch 237 | # You have to put your server's BLE address here, see server's startup log 238 | mac_light: "f4:12:fa:61:00:f5" 239 | switch_button_id: button0 240 | light_peer_id: demo_light 241 | light_virtual_switch_uuid: 32f40d3a-d24d-11ed-afa1-0242ac120002 242 | light_control_service_uuid: 2dc01d40-d24d-11ed-afa1-0242ac120002 243 | 244 | ble_client: 245 | - mac_address: ${mac_light} 246 | id: ${light_peer_id}_client 247 | on_connect: 248 | then: 249 | - lambda: |- 250 | ESP_LOGD("ble_client", "Connected to ${light_peer_id}"); 251 | 252 | binary_sensor: 253 | - platform: gpio 254 | pin: 255 | number: GPIO41 256 | id: ${switch_button_id} 257 | name: ${switch_button_id} 258 | icon: "mdi:gesture-tap-button" 259 | on_release: 260 | then: 261 | - logger.log: "Toggling the light using BLE" 262 | - ble_client.ble_write: 263 | id: ${light_peer_id}_client 264 | service_uuid: ${light_control_service_uuid} 265 | characteristic_uuid: ${light_virtual_switch_uuid} 266 | value: 1 267 | ``` 268 | 269 | ### Light configuration (BLE server) 270 | 271 | ```yaml 272 | substitutions: 273 | hostname: demo-light-ble 274 | device_id: demo_light_ble 275 | mac_light: "f4:12:fa:61:00:f5" 276 | light_virtual_switch_uuid: 32f40d3a-d24d-11ed-afa1-0242ac120002 277 | light_control_service_uuid: 2dc01d40-d24d-11ed-afa1-0242ac120002 278 | light_toggle_command: demo-toggle 279 | light_virtual_switch_id: light_virtual_switch 280 | 281 | esp32_ble_controller: 282 | security_mode: none 283 | services: 284 | - service: ${light_control_service_uuid} 285 | characteristics: 286 | - characteristic: ${light_virtual_switch_uuid} 287 | exposes: ${light_virtual_switch_id} 288 | # The commands and the events below are for debugging purposes. 289 | commands: 290 | - command: ${light_toggle_command} 291 | description: Toggle the light demo 292 | on_execute: 293 | - logger.log: "Toggle executed" 294 | - light.toggle: status_led 295 | on_connected: 296 | - logger.log: "Connected." 297 | on_disconnected: 298 | - logger.log: "Disconnected." 299 | 300 | switch: 301 | - platform: template 302 | id: ${light_virtual_switch_id} 303 | turn_on_action: 304 | - logger.log: "Switch ON" 305 | - light.toggle: status_led 306 | turn_off_action: 307 | - logger.log: "Switch OFF" 308 | - light.toggle: status_led 309 | 310 | light: 311 | - platform: neopixelbus 312 | num_leds: 1 313 | variant: WS2812 314 | pin: GPIO35 315 | id: status_led 316 | name: ${hostname} RGB LED 317 | default_transition_length: 318 | milliseconds: 0 319 | ``` 320 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/__init__.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import esphome.codegen as cg 4 | import esphome.config_validation as cv 5 | from esphome.automation import LambdaAction 6 | from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FORMAT, CONF_ARGS 7 | from esphome import automation 8 | from esphome.core import coroutine, Lambda 9 | from esphome.cpp_generator import MockObj 10 | 11 | CODEOWNERS = ['@wifwucite'] 12 | 13 | ### Shared stuff ######################################################################################################## 14 | 15 | esp32_ble_controller_ns = cg.esphome_ns.namespace('esp32_ble_controller') 16 | ESP32BLEController = esp32_ble_controller_ns.class_('ESP32BLEController', cg.Component, cg.Controller) 17 | 18 | ### Configuration validation ############################################################################################ 19 | 20 | # BLE component services and characteristics ##### 21 | CONF_BLE_SERVICES = "services" 22 | CONF_BLE_SERVICE = "service" 23 | CONF_BLE_CHARACTERISTICS = "characteristics" 24 | CONF_BLE_CHARACTERISTIC = "characteristic" 25 | CONF_BLE_USE_2902 = "use_BLE2902" 26 | CONF_EXPOSES_COMPONENT = "exposes" 27 | 28 | def validate_UUID(value): 29 | # print("UUID«", value) 30 | value = cv.string(value) 31 | # TASK improve the regex 32 | if re.match(r'^[0-9a-fA-F\-]{8,36}$', value) is None: 33 | raise cv.Invalid("valid UUID required") 34 | return value 35 | 36 | BLE_CHARACTERISTIC = cv.Schema({ 37 | cv.Required("characteristic"): validate_UUID, 38 | cv.GenerateID(CONF_EXPOSES_COMPONENT): cv.use_id(cg.EntityBase), # TASK validate that only supported EntityBase instances are referenced 39 | cv.Optional(CONF_BLE_USE_2902, default=True): cv.boolean, 40 | }) 41 | 42 | BLE_SERVICE = cv.Schema({ 43 | cv.Required(CONF_BLE_SERVICE): validate_UUID, 44 | cv.Required(CONF_BLE_CHARACTERISTICS): cv.ensure_list(BLE_CHARACTERISTIC), 45 | }) 46 | 47 | # custom commands ##### 48 | CONF_BLE_COMMANDS = "commands" 49 | CONF_BLE_CMD_ID = "command" 50 | CONF_BLE_CMD_DESCRIPTION = "description" 51 | CONF_BLE_CMD_ON_EXECUTE = "on_execute" 52 | BLEControllerCustomCommandExecutionTrigger = esp32_ble_controller_ns.class_('BLEControllerCustomCommandExecutionTrigger', automation.Trigger.template()) 53 | 54 | BUILTIN_CMD_IDS = ['help', 'ble-services', 'wifi-config', 'pairings', 'version', 'log-level'] 55 | CMD_ID_CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789-" 56 | def validate_command_id(value): 57 | """Validate that this value is a valid command id. 58 | """ 59 | value = cv.string_strict(value).lower() 60 | if value in BUILTIN_CMD_IDS: 61 | raise cv.Invalid(f"{value} is a built-in command") 62 | for c in value: 63 | if c not in CMD_ID_CHARACTERS: 64 | raise cv.Invalid(f"Invalid character for command id: {c}") 65 | return value 66 | 67 | BLE_COMMAND = cv.Schema({ 68 | cv.Required(CONF_BLE_CMD_ID): validate_command_id, 69 | cv.Required(CONF_BLE_CMD_DESCRIPTION): cv.string_strict, 70 | cv.Required(CONF_BLE_CMD_ON_EXECUTE): automation.validate_automation({ 71 | cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEControllerCustomCommandExecutionTrigger), 72 | }), 73 | }) 74 | 75 | # BLE maintenance services ##### 76 | CONF_EXPOSE_MAINTENANCE_SERVICE = "maintenance" 77 | 78 | # security mode enumeration ##### 79 | CONF_SECURITY_MODE = 'security_mode' 80 | BLESecurityMode = esp32_ble_controller_ns.enum("BLESecurityMode", is_class = True) 81 | CONF_SECURITY_MODE_NONE = 'none' # no security, no bonding 82 | CONF_SECURITY_MODE_BOND = 'bond' # no secure connection, no man-in-the-middle protection, just bonding (pairing) 83 | CONF_SECURITY_MODE_SECURE = 'secure' # default: bonding, secure connection, MITM protection 84 | SECURTY_MODE_OPTIONS = { 85 | CONF_SECURITY_MODE_NONE: BLESecurityMode.NONE, 86 | CONF_SECURITY_MODE_BOND: BLESecurityMode.BOND, 87 | CONF_SECURITY_MODE_SECURE: BLESecurityMode.SECURE, 88 | } 89 | 90 | # authetication and (dis)connected automations ##### 91 | CONF_ON_SHOW_PASS_KEY = "on_show_pass_key" 92 | BLEControllerShowPassKeyTrigger = esp32_ble_controller_ns.class_('BLEControllerShowPassKeyTrigger', automation.Trigger.template()) 93 | 94 | CONF_ON_AUTHENTICATION_COMPLETE = "on_authentication_complete" 95 | BLEControllerAuthenticationCompleteTrigger = esp32_ble_controller_ns.class_('BLEControllerAuthenticationCompleteTrigger', automation.Trigger.template()) 96 | 97 | def forbid_config_setting_for_automation(automation_id, setting_key, forbidden_setting_value, config): 98 | """Validates that a given automation is only present if a given setting does not have a given value.""" 99 | if automation_id in config and config[setting_key] == forbidden_setting_value: 100 | raise cv.Invalid("Automation '" + automation_id + "' not available if " + setting_key + " = " + forbidden_setting_value) 101 | 102 | def automations_available(config): 103 | """Validates that the security related automations are only present if the security mode is not none.""" 104 | forbid_config_setting_for_automation(CONF_ON_SHOW_PASS_KEY, CONF_SECURITY_MODE, CONF_SECURITY_MODE_NONE, config) 105 | forbid_config_setting_for_automation(CONF_ON_AUTHENTICATION_COMPLETE, CONF_SECURITY_MODE, CONF_SECURITY_MODE_NONE, config) 106 | return config 107 | 108 | def require_automation_for_config_setting(automation_id, setting_key, requiring_setting_value, config): 109 | """Validates that a given automation is only present if a given setting does not have a given value.""" 110 | if config[setting_key] == requiring_setting_value and not automation_id in config: 111 | raise cv.Invalid("Automation '" + automation_id + "' required if " + setting_key + " = " + requiring_setting_value) 112 | 113 | def required_automations_present(config): 114 | """Validates that the pass key related automation is present if the security mode is set to secure.""" 115 | require_automation_for_config_setting(CONF_ON_SHOW_PASS_KEY, CONF_SECURITY_MODE, CONF_SECURITY_MODE_SECURE, config) 116 | return config 117 | 118 | CONF_ON_SERVER_CONNECTED = "on_connected" 119 | BLEControllerServerConnectedTrigger = esp32_ble_controller_ns.class_('BLEControllerServerConnectedTrigger', automation.Trigger.template()) 120 | 121 | CONF_ON_SERVER_DISCONNECTED = "on_disconnected" 122 | BLEControllerServerDisconnectedTrigger = esp32_ble_controller_ns.class_('BLEControllerServerDisconnectedTrigger', automation.Trigger.template()) 123 | 124 | # Schema for the controller (incl. validation) ##### 125 | CONFIG_SCHEMA = cv.All(cv.only_on_esp32, cv.only_with_arduino, cv.Schema({ 126 | cv.GenerateID(): cv.declare_id(ESP32BLEController), 127 | 128 | cv.Optional(CONF_BLE_SERVICES): cv.ensure_list(BLE_SERVICE), 129 | 130 | cv.Optional(CONF_BLE_COMMANDS): cv.ensure_list(BLE_COMMAND), 131 | 132 | cv.Optional(CONF_EXPOSE_MAINTENANCE_SERVICE, default=True): cv.boolean, 133 | 134 | cv.Optional(CONF_SECURITY_MODE, default=CONF_SECURITY_MODE_SECURE): cv.enum(SECURTY_MODE_OPTIONS), 135 | 136 | cv.Optional(CONF_ON_SHOW_PASS_KEY): automation.validate_automation({ 137 | cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEControllerShowPassKeyTrigger), 138 | }), 139 | cv.Optional(CONF_ON_AUTHENTICATION_COMPLETE): automation.validate_automation({ 140 | cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEControllerAuthenticationCompleteTrigger), 141 | }), 142 | 143 | cv.Optional(CONF_ON_SERVER_CONNECTED): automation.validate_automation({ 144 | cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEControllerServerConnectedTrigger), 145 | }), 146 | cv.Optional(CONF_ON_SERVER_DISCONNECTED): automation.validate_automation({ 147 | cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEControllerServerDisconnectedTrigger), 148 | }), 149 | 150 | }), automations_available, required_automations_present) 151 | 152 | ### Code generation ############################################################################################ 153 | 154 | @coroutine 155 | def to_code_characteristic(ble_controller_var, service_uuid, characteristic_description): 156 | """Coroutine that registers the given characteristic of the given service with BLE controller, 157 | i.e. generates a single controller->register_component(...) call""" 158 | characteristic_uuid = characteristic_description[CONF_BLE_CHARACTERISTIC] 159 | component_id = characteristic_description[CONF_EXPOSES_COMPONENT] 160 | component = yield cg.get_variable(component_id) 161 | use_BLE2902 = characteristic_description[CONF_BLE_USE_2902] 162 | cg.add(ble_controller_var.register_component(component, service_uuid, characteristic_uuid, use_BLE2902)) 163 | 164 | @coroutine 165 | def to_code_service(ble_controller_var, service): 166 | """Coroutine that registers all characteristics of the given service with BLE controller""" 167 | service_uuid = service[CONF_BLE_SERVICE] 168 | characteristics = service[CONF_BLE_CHARACTERISTICS] 169 | for characteristic_description in characteristics: 170 | yield to_code_characteristic(ble_controller_var, service_uuid, characteristic_description) 171 | 172 | @coroutine 173 | def to_code_command(ble_controller_var, cmd): 174 | """Coroutine that registers all BLE commands with BLE controller""" 175 | id = cmd[CONF_BLE_CMD_ID] 176 | description = cmd[CONF_BLE_CMD_DESCRIPTION] 177 | trigger_conf = cmd[CONF_BLE_CMD_ON_EXECUTE][0] 178 | trigger = cg.new_Pvariable(trigger_conf[CONF_TRIGGER_ID], ble_controller_var) 179 | yield automation.build_automation(trigger, [(cg.std_ns.class_("vector"), 'arguments'), (esp32_ble_controller_ns.class_("BLECustomCommandResultSender"), 'result')], trigger_conf) 180 | cg.add(ble_controller_var.register_command(id, description, trigger)) 181 | 182 | def to_code(config): 183 | """Generates the C++ code for the BLE controller configuration""" 184 | var = cg.new_Pvariable(config[CONF_ID]) 185 | yield cg.register_component(var, config) 186 | 187 | for cmd in config.get(CONF_BLE_SERVICES, []): 188 | yield to_code_service(var, cmd) 189 | 190 | for cmd in config.get(CONF_BLE_COMMANDS, []): 191 | yield to_code_command(var, cmd) 192 | 193 | cg.add(var.set_maintenance_service_exposed_after_flash(config[CONF_EXPOSE_MAINTENANCE_SERVICE])) 194 | 195 | security_enabled = SECURTY_MODE_OPTIONS[config[CONF_SECURITY_MODE]] 196 | cg.add(var.set_security_mode(config[CONF_SECURITY_MODE])) 197 | 198 | for conf in config.get(CONF_ON_SHOW_PASS_KEY, []): 199 | trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) 200 | yield automation.build_automation(trigger, [(cg.std_string, 'pass_key')], conf) 201 | 202 | for conf in config.get(CONF_ON_AUTHENTICATION_COMPLETE, []): 203 | trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) 204 | yield automation.build_automation(trigger, [(cg.bool_, 'success')], conf) 205 | 206 | for conf in config.get(CONF_ON_SERVER_CONNECTED, []): 207 | trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) 208 | yield automation.build_automation(trigger, [], conf) 209 | 210 | for conf in config.get(CONF_ON_SERVER_DISCONNECTED, []): 211 | trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) 212 | yield automation.build_automation(trigger, [], conf) 213 | 214 | # if there are incompatilibities with the framework set "lib_ldf_mode = deep" in platformio.ini and check the version 215 | cg.add_library("ESP32 BLE Arduino", "2.0.0"); 216 | 217 | ### Automation actions ############################################################################################ 218 | 219 | GLOBAL_BLE_CONTROLLER_VAR = MockObj(esp32_ble_controller_ns.global_ble_controller, "->") 220 | 221 | ### Automation action: ble_cmd.send_result ### 222 | 223 | def maybe_simple_message(schema): 224 | def validator(value): 225 | if isinstance(value, dict): 226 | return cv.Schema(schema)(value) 227 | return cv.Schema(schema)({CONF_FORMAT: value}) 228 | 229 | return validator 230 | 231 | def validate_printf(value): 232 | # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python 233 | # pylint: disable=anomalous-backslash-in-string 234 | cfmt = """\ 235 | ( # start of capture group 1 236 | % # literal "%" 237 | (?:[-+0 #]{0,5}) # optional flags 238 | (?:\d+|\*)? # width 239 | (?:\.(?:\d+|\*))? # precision 240 | (?:h|l|ll|w|I|I32|I64)? # size 241 | [cCdiouxXeEfgGaAnpsSZ] # type 242 | ) 243 | """ # noqa 244 | matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X) 245 | if len(matches) != len(value[CONF_ARGS]): 246 | raise cv.Invalid( 247 | "Found {} printf-patterns ({}), but {} args were given!" 248 | "".format(len(matches), ", ".join(matches), len(value[CONF_ARGS])) 249 | ) 250 | return value 251 | 252 | BLE_CMD_SET_RESULT_ACTION_SCHEMA = cv.All( 253 | maybe_simple_message( 254 | { 255 | cv.Required(CONF_FORMAT): cv.string, 256 | cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_), 257 | } 258 | ), 259 | validate_printf, 260 | ) 261 | 262 | @automation.register_action("ble_cmd.send_result", LambdaAction, BLE_CMD_SET_RESULT_ACTION_SCHEMA) 263 | async def ble_cmd_set_result_action_to_code(config, action_id, template_arg, args): 264 | args_ = [cg.RawExpression(str(x)) for x in config[CONF_ARGS]] 265 | 266 | text = str(cg.statement(GLOBAL_BLE_CONTROLLER_VAR.send_command_result(config[CONF_FORMAT], *args_))) 267 | 268 | lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) 269 | return cg.new_Pvariable(action_id, template_arg, lambda_) 270 | 271 | ### Automation action: ble_maintenance.toggle/turn_on/turn_off ### 272 | 273 | ToggleAction = esp32_ble_controller_ns.class_("ToggleMaintenanceServiceAction", automation.Action) 274 | TurnOffAction = esp32_ble_controller_ns.class_("TurnOffMaintenanceServiceAction", automation.Action) 275 | TurnOnAction = esp32_ble_controller_ns.class_("TurnOnMaintenanceServiceAction", automation.Action) 276 | 277 | BLE_MAINTENANCE_ACTION_SCHEMA = cv.Schema({}) # no arguments required 278 | 279 | @automation.register_action("ble_maintenance.toggle", ToggleAction, BLE_MAINTENANCE_ACTION_SCHEMA) 280 | @automation.register_action("ble_maintenance.turn_off", TurnOffAction, BLE_MAINTENANCE_ACTION_SCHEMA) 281 | @automation.register_action("ble_maintenance.turn_on", TurnOnAction, BLE_MAINTENANCE_ACTION_SCHEMA) 282 | async def ble_maintenance_toggle_to_code(config, action_id, template_arg, args): 283 | print(config, action_id, template_arg, args) 284 | return cg.new_Pvariable(action_id, template_arg) 285 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "esphome/core/automation.h" 6 | #include "esphome/core/optional.h" 7 | 8 | #include "esp32_ble_controller.h" 9 | 10 | using std::shared_ptr; 11 | 12 | namespace esphome { 13 | namespace esp32_ble_controller { 14 | 15 | // authentication /////////////////////////////////////////////////////////////////////////////////////////////// 16 | 17 | /// Trigger for showing the pass key during authentication with a client. 18 | class BLEControllerShowPassKeyTrigger : public Trigger { 19 | public: 20 | BLEControllerShowPassKeyTrigger(ESP32BLEController* controller) { 21 | controller->add_on_show_pass_key_callback([this](string pass_key) { 22 | this->trigger(pass_key); 23 | }); 24 | } 25 | }; 26 | 27 | /// Trigger that is fired when authentication with a client is completed (either with success or failure). 28 | class BLEControllerAuthenticationCompleteTrigger : public Trigger { 29 | public: 30 | BLEControllerAuthenticationCompleteTrigger(ESP32BLEController* controller) { 31 | controller->add_on_authentication_complete_callback([this](bool success) { 32 | this->trigger(success); 33 | }); 34 | } 35 | }; 36 | 37 | // connect & disconnect /////////////////////////////////////////////////////////////////////////////////////////////// 38 | 39 | /// Trigger that is fired when the BLE server has connected to a client. 40 | class BLEControllerServerConnectedTrigger : public Trigger<> { 41 | public: 42 | BLEControllerServerConnectedTrigger(ESP32BLEController* controller) { 43 | controller->add_on_connected_callback([this]() { 44 | this->trigger(); 45 | }); 46 | } 47 | }; 48 | 49 | /// Trigger that is fired when the BLE server has disconnected from a client. 50 | class BLEControllerServerDisconnectedTrigger : public Trigger<> { 51 | public: 52 | BLEControllerServerDisconnectedTrigger(ESP32BLEController* controller) { 53 | controller->add_on_disconnected_callback([this]() { 54 | this->trigger(); 55 | }); 56 | } 57 | }; 58 | 59 | // custom command execution /////////////////////////////////////////////////////////////////////////////////////////////// 60 | 61 | class BLECustomCommandResultSender { 62 | public: 63 | void operator=(const string& result) { global_ble_controller->send_command_result(result); } 64 | }; 65 | 66 | /// Trigger that is fired when a custom command is executed. 67 | class BLEControllerCustomCommandExecutionTrigger : public Trigger, BLECustomCommandResultSender> { 68 | public: 69 | BLEControllerCustomCommandExecutionTrigger(ESP32BLEController* controller) {} 70 | }; 71 | 72 | // actions for BLE maintenance service //////////////////////////////////////////////////////////////////////////////////// 73 | 74 | template class TurnOnMaintenanceServiceAction : public Action { 75 | public: 76 | void play(Ts... x) override { global_ble_controller->switch_maintenance_service_exposed(true); } 77 | }; 78 | 79 | template class TurnOffMaintenanceServiceAction : public Action { 80 | public: 81 | void play(Ts... x) override { global_ble_controller->switch_maintenance_service_exposed(false); } 82 | }; 83 | 84 | template class ToggleMaintenanceServiceAction : public Action { 85 | public: 86 | void play(Ts... x) override { 87 | const bool exposed = global_ble_controller->get_maintenance_service_exposed(); 88 | global_ble_controller->switch_maintenance_service_exposed(!exposed); 89 | } 90 | }; 91 | 92 | } // namespace esp32_ble_controller 93 | } // namespace esphome 94 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_command.cpp: -------------------------------------------------------------------------------- 1 | #include "ble_command.h" 2 | 3 | #include "esphome/core/application.h" 4 | 5 | #include "esp32_ble_controller.h" 6 | #include "automation.h" 7 | #include "ble_utils.h" 8 | 9 | namespace esphome { 10 | namespace esp32_ble_controller { 11 | 12 | static const char *TAG = "ble_command"; 13 | 14 | // generic /////////////////////////////////////////////////////////////////////////////////////////////// 15 | 16 | void BLECommand::set_result(const string& result) const { 17 | global_ble_controller->send_command_result(result); 18 | } 19 | 20 | string BLECommand::get_command_specific_help() const { 21 | return get_description(); 22 | } 23 | 24 | // help /////////////////////////////////////////////////////////////////////////////////////////////// 25 | 26 | BLECommandHelp::BLECommandHelp() : BLECommand("help", "shows help for commands.") {} 27 | 28 | void BLECommandHelp::execute(const vector& arguments) const { 29 | if (arguments.empty()) { 30 | string help("Availabe:"); 31 | for (const auto& command : global_ble_controller->get_commands()) { 32 | help += ' '; 33 | help += command->get_name(); 34 | } 35 | help += ", 'help ' for more."; 36 | set_result(help); 37 | } else { 38 | string command_name = arguments[0]; 39 | for (const auto& command : global_ble_controller->get_commands()) { 40 | if (command->get_name() == command_name) { 41 | set_result(command_name + ": " + command->get_command_specific_help()); 42 | return; 43 | } 44 | set_result("Unknown BLE command '" + command_name + "'"); 45 | } 46 | } 47 | } 48 | 49 | // ble-maintenance /////////////////////////////////////////////////////////////////////////////////////////////// 50 | 51 | BLECommandSwitchMaintenanceOnOrOff::BLECommandSwitchMaintenanceOnOrOff() : BLECommand("ble-maintenance", "'ble-maintenance off' disables the maintenance BLE service.") {} 52 | 53 | void BLECommandSwitchMaintenanceOnOrOff::execute(const vector& arguments) const { 54 | if (!arguments.empty()) { 55 | const string& on_or_off = arguments[0]; 56 | global_ble_controller->switch_maintenance_service_exposed(on_or_off != "off"); 57 | } 58 | string enabled_or_disabled = global_ble_controller->get_component_services_exposed() ? "disabled" : "enabled"; 59 | set_result("Maintenance services is " + enabled_or_disabled +"."); 60 | } 61 | 62 | // ble-services /////////////////////////////////////////////////////////////////////////////////////////////// 63 | 64 | BLECommandSwitchComponentServicesOnOrOff::BLECommandSwitchComponentServicesOnOrOff() : BLECommand("ble-services", "'ble-services on|off' enables or disables the non-maintenance BLE services.") {} 65 | 66 | void BLECommandSwitchComponentServicesOnOrOff::execute(const vector& arguments) const { 67 | if (!arguments.empty()) { 68 | const string& on_or_off = arguments[0]; 69 | global_ble_controller->switch_component_services_exposed(on_or_off != "off"); 70 | } 71 | string enabled_or_disabled = global_ble_controller->get_component_services_exposed() ? "disabled" : "enabled"; 72 | set_result("Non-maintenance services are " + enabled_or_disabled +"."); 73 | } 74 | 75 | // wifi-config /////////////////////////////////////////////////////////////////////////////////////////////// 76 | 77 | #ifdef USE_WIFI 78 | BLECommandWifiConfiguration::BLECommandWifiConfiguration() : BLECommand("wifi-config", "sets or clears the WIFI configuration") {} 79 | 80 | void BLECommandWifiConfiguration::execute(const vector& arguments) const { 81 | if (arguments.size() >= 2 && arguments.size() <= 3) { 82 | const string& ssid = arguments[0]; 83 | const string& password = arguments[1]; 84 | const bool hidden_network = arguments.size() == 3 && arguments[2] == "hidden"; 85 | global_ble_controller->set_wifi_configuration(ssid, password, hidden_network); 86 | set_result("WIFI configuration updated."); 87 | } else if (arguments.size() == 1 && arguments[0] == "clear") { 88 | set_result("WIFI configuration cleared."); 89 | global_ble_controller->clear_wifi_configuration_and_reboot(); 90 | } else if (arguments.empty()) { 91 | auto ssid = global_ble_controller->get_current_ssid_in_wifi_configuration(); 92 | if (ssid.has_value()) { 93 | set_result("WIFI configuration present for network " + ssid.value() + "."); 94 | } else { 95 | set_result("WIFI configuration not overridden, default used."); 96 | } 97 | } else { 98 | set_result("Invalid arguments!"); 99 | } 100 | } 101 | 102 | string BLECommandWifiConfiguration::get_command_specific_help() const { 103 | auto ssid = global_ble_controller->get_current_ssid_in_wifi_configuration(); 104 | if (ssid.has_value()) { 105 | return "'wifi-config clear' clears the configuration and reverts to default WIFI configuration."; 106 | } else { 107 | return "'wifi-config [hidden]' sets WIFI SSID and password and if the network is hidden."; 108 | } 109 | } 110 | #endif 111 | 112 | // pairings /////////////////////////////////////////////////////////////////////////////////////////////// 113 | 114 | BLECommandPairings::BLECommandPairings() : BLECommand("pairings", "'pairings [clear]' displays or clears the paired devices.") {} 115 | 116 | void BLECommandPairings::execute(const vector& arguments) const { 117 | if (!arguments.empty()) { 118 | if (arguments[0] == "clear") { 119 | remove_all_bonded_devices(); 120 | set_result("Pairings cleared."); 121 | return; 122 | } 123 | } 124 | 125 | vector paired_devices = get_bonded_devices(); 126 | if (paired_devices.empty()) { 127 | set_result("No paired devices."); 128 | } else { 129 | string paired_devices_listing = "Paired devices:"; 130 | for (const string& paired_device : paired_devices) { 131 | paired_devices_listing += " "; 132 | paired_devices_listing += paired_device; 133 | } 134 | set_result(paired_devices_listing +"."); 135 | } 136 | } 137 | 138 | // version /////////////////////////////////////////////////////////////////////////////////////////////// 139 | 140 | BLECommandVersion::BLECommandVersion() : BLECommand("version", "displays the current version, i.e. compile time.") {} 141 | 142 | void BLECommandVersion::execute(const vector& arguments) const { 143 | set_result("Version: " + App.get_compilation_time()); 144 | } 145 | 146 | // log-level /////////////////////////////////////////////////////////////////////////////////////////////// 147 | 148 | #ifdef USE_LOGGER 149 | BLECommandLogLevel::BLECommandLogLevel() : BLECommand("log-level", "gets or sets log level (0=None, 4=Config, 5=Debug).") {} 150 | 151 | void BLECommandLogLevel::execute(const vector& arguments) const { 152 | if (!arguments.empty()) { 153 | string log_level = arguments[0]; 154 | const optional level = parse_number(log_level); 155 | if (level.has_value()) { 156 | global_ble_controller->set_log_level(level.value()); 157 | } 158 | } 159 | 160 | set_result("Log level is " + to_string(global_ble_controller->get_log_level())+"."); 161 | } 162 | #endif 163 | 164 | // custom /////////////////////////////////////////////////////////////////////////////////////////////// 165 | 166 | BLECustomCommand::BLECustomCommand(const string& name, const string& description, BLEControllerCustomCommandExecutionTrigger* trigger) 167 | : BLECommand(name, description), trigger(trigger) {} 168 | 169 | void BLECustomCommand::execute(const vector& arguments) const { 170 | BLECustomCommandResultSender result_sender; 171 | trigger->trigger(arguments, result_sender); 172 | } 173 | 174 | } // namespace esp32_ble_controller 175 | } // namespace esphome 176 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_command.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "esphome/core/defines.h" 7 | 8 | using std::string; 9 | using std::vector; 10 | 11 | namespace esphome { 12 | namespace esp32_ble_controller { 13 | 14 | // generic /////////////////////////////////////////////////////////////////////////////////////////////// 15 | 16 | class BLECommand { 17 | public: 18 | BLECommand(const string& name, const string& description) : name(name), description(description) {} 19 | virtual ~BLECommand() {} 20 | 21 | const string get_name() const { return name; } 22 | const string get_description() const { return description; } 23 | 24 | virtual void execute(const std::vector& arguments) const = 0; 25 | 26 | virtual string get_command_specific_help() const; 27 | 28 | protected: 29 | void set_result(const string& result) const; 30 | 31 | private: 32 | string name; 33 | string description; 34 | }; 35 | 36 | // help /////////////////////////////////////////////////////////////////////////////////////////////// 37 | 38 | class BLECommandHelp : public BLECommand { 39 | public: 40 | BLECommandHelp(); 41 | virtual ~BLECommandHelp() {} 42 | 43 | virtual void execute(const vector& arguments) const override; 44 | }; 45 | 46 | // ble-maintenance /////////////////////////////////////////////////////////////////////////////////////////////// 47 | 48 | class BLECommandSwitchMaintenanceOnOrOff : public BLECommand { 49 | public: 50 | BLECommandSwitchMaintenanceOnOrOff(); 51 | virtual ~BLECommandSwitchMaintenanceOnOrOff() {} 52 | 53 | virtual void execute(const vector& arguments) const override; 54 | }; 55 | 56 | // ble-services /////////////////////////////////////////////////////////////////////////////////////////////// 57 | 58 | class BLECommandSwitchComponentServicesOnOrOff : public BLECommand { 59 | public: 60 | BLECommandSwitchComponentServicesOnOrOff(); 61 | virtual ~BLECommandSwitchComponentServicesOnOrOff() {} 62 | 63 | virtual void execute(const vector& arguments) const override; 64 | }; 65 | 66 | // wifi-config /////////////////////////////////////////////////////////////////////////////////////////////// 67 | 68 | #ifdef USE_WIFI 69 | class BLECommandWifiConfiguration : public BLECommand { 70 | public: 71 | BLECommandWifiConfiguration(); 72 | virtual ~BLECommandWifiConfiguration() {} 73 | 74 | virtual void execute(const vector& arguments) const override; 75 | 76 | virtual string get_command_specific_help() const override; 77 | }; 78 | #endif 79 | 80 | // pairings /////////////////////////////////////////////////////////////////////////////////////////////// 81 | 82 | class BLECommandPairings : public BLECommand { 83 | public: 84 | BLECommandPairings(); 85 | virtual ~BLECommandPairings() {} 86 | 87 | virtual void execute(const vector& arguments) const override; 88 | }; 89 | 90 | // version /////////////////////////////////////////////////////////////////////////////////////////////// 91 | 92 | class BLECommandVersion : public BLECommand { 93 | public: 94 | BLECommandVersion(); 95 | virtual ~BLECommandVersion() {} 96 | 97 | virtual void execute(const vector& arguments) const override; 98 | }; 99 | 100 | // log-level /////////////////////////////////////////////////////////////////////////////////////////////// 101 | 102 | #ifdef USE_LOGGER 103 | class BLECommandLogLevel : public BLECommand { 104 | public: 105 | BLECommandLogLevel(); 106 | virtual ~BLECommandLogLevel() {} 107 | 108 | virtual void execute(const vector& arguments) const override; 109 | }; 110 | #endif 111 | 112 | // custom /////////////////////////////////////////////////////////////////////////////////////////////// 113 | 114 | class BLEControllerCustomCommandExecutionTrigger; 115 | 116 | class BLECustomCommand : public BLECommand { 117 | public: 118 | BLECustomCommand(const string& name, const string& description, BLEControllerCustomCommandExecutionTrigger* trigger); 119 | virtual ~BLECustomCommand() {} 120 | 121 | virtual void execute(const vector& arguments) const override; 122 | 123 | private: 124 | BLEControllerCustomCommandExecutionTrigger* trigger; 125 | }; 126 | 127 | } // namespace esp32_ble_controller 128 | } // namespace esphome 129 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_component_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ble_component_handler_base.h" 4 | 5 | using std::string; 6 | 7 | namespace esphome { 8 | namespace esp32_ble_controller { 9 | 10 | /** 11 | * A component handler controls a single component (sensor, switch, ...). 12 | * Each component corresponds one-to-one to a BLE characteristic. When the state of the component changes in ESPHome, this handler updates the characteristic 13 | * so that a BLE client gets notified about the change and can read the new value. 14 | * Some components like switch can be manipulated by the BLE client, i.e. the client writes a new value to the characteristic, which this handler observes and 15 | * performs the required changes like turning on the switch. 16 | * 17 | * Note: This is a type-safe variant of its base class. 18 | * @brief Controls a single component (sensor, switch, ...), propagates state changes to the BLE client and executes change request from the client. 19 | */ 20 | template 21 | class BLEComponentHandler : public BLEComponentHandlerBase { 22 | public: 23 | BLEComponentHandler(C* component, const BLECharacteristicInfoForHandler& characteristic_info) : BLEComponentHandlerBase(component, characteristic_info) {} 24 | virtual ~BLEComponentHandler() {} 25 | 26 | protected: 27 | virtual C* get_component() override { return static_cast(BLEComponentHandlerBase::get_component()); } 28 | 29 | }; 30 | 31 | } // namespace esp32_ble_controller 32 | } // namespace esphome 33 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_component_handler_base.cpp: -------------------------------------------------------------------------------- 1 | #include "ble_component_handler_base.h" 2 | 3 | #include 4 | 5 | #include "esphome/core/log.h" 6 | 7 | #include "esp32_ble_controller.h" 8 | #include "ble_utils.h" 9 | 10 | namespace esphome { 11 | namespace esp32_ble_controller { 12 | 13 | static const char *TAG = "ble_component_handler_base"; 14 | 15 | BLEComponentHandlerBase::BLEComponentHandlerBase(EntityBase* component, const BLECharacteristicInfoForHandler& characteristic_info) 16 | : component(component), characteristic_info(characteristic_info) 17 | {} 18 | 19 | BLEComponentHandlerBase::~BLEComponentHandlerBase() 20 | {} 21 | 22 | void BLEComponentHandlerBase::setup(BLEServer* ble_server) { 23 | const string& object_id = component->get_object_id(); 24 | 25 | ESP_LOGCONFIG(TAG, "Setting up BLE characteristic for component %s", object_id.c_str()); 26 | 27 | // Get or create the BLE service. 28 | const string& service_UUID = characteristic_info.service_UUID; 29 | BLEService* service = ble_server->getServiceByUUID(service_UUID); 30 | if (service == nullptr) { 31 | service = ble_server->createService(service_UUID); 32 | } 33 | 34 | // Create the BLE characteristic. 35 | const string& characteristic_UUID = characteristic_info.characteristic_UUID; 36 | if (can_receive_writes()) { 37 | characteristic = create_writeable_ble_characteristic(service, characteristic_UUID, this, get_component_description(), characteristic_info.use_BLE2902); 38 | } else { 39 | characteristic = create_read_only_ble_characteristic(service, characteristic_UUID, get_component_description(), characteristic_info.use_BLE2902); 40 | } 41 | 42 | service->start(); 43 | 44 | ESP_LOGCONFIG(TAG, "%s: SRV %s - CHAR %s", object_id.c_str(), service_UUID.c_str(), characteristic_UUID.c_str()); 45 | } 46 | 47 | void BLEComponentHandlerBase::send_value(float value) { 48 | const string& object_id = component->get_object_id(); 49 | ESP_LOGD(TAG, "Update component %s to %f", object_id.c_str(), value); 50 | 51 | characteristic->setValue(value); 52 | characteristic->notify(); 53 | } 54 | 55 | void BLEComponentHandlerBase::send_value(string value) { 56 | const string& object_id = component->get_object_id(); 57 | ESP_LOGD(TAG, "Update component %s to %s", object_id.c_str(), value.c_str()); 58 | 59 | characteristic->setValue(value); 60 | characteristic->notify(); 61 | } 62 | 63 | void BLEComponentHandlerBase::send_value(bool raw_value) { 64 | const string& object_id = component->get_object_id(); 65 | ESP_LOGD(TAG, "Update component %s to %d", object_id.c_str(), raw_value); 66 | 67 | uint16_t value = raw_value; 68 | characteristic->setValue(value); 69 | characteristic->notify(); 70 | } 71 | 72 | void BLEComponentHandlerBase::onWrite(BLECharacteristic *characteristic) { 73 | global_ble_controller->execute_in_loop([this](){ on_characteristic_written(); }); 74 | } 75 | 76 | bool BLEComponentHandlerBase::is_security_enabled() { 77 | return global_ble_controller->get_security_enabled(); 78 | } 79 | 80 | } // namespace esp32_ble_controller 81 | } // namespace esphome 82 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_component_handler_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "esphome/core/entity_base.h" 9 | #include "esphome/core/controller.h" 10 | #include "esphome/core/defines.h" 11 | 12 | using std::string; 13 | 14 | namespace esphome { 15 | namespace esp32_ble_controller { 16 | 17 | struct BLECharacteristicInfoForHandler { 18 | string service_UUID; 19 | string characteristic_UUID; 20 | bool use_BLE2902; 21 | }; 22 | 23 | /** 24 | * A component handler controls a single component (sensor, switch, ...). 25 | * Each component corresponds one-to-one to a BLE characteristic. When the state of the component changes in ESPHome, this handler updates the characteristic 26 | * so that a BLE client gets notified about the change and can read the new value. 27 | * Some components like switch can be manipulated by the BLE client, i.e. the client writes a new value to the characteristic, which this handler observes and 28 | * performs the required changes like turning on the switch. 29 | * @brief Controls a single component (sensor, switch, ...), propagates state changes to the BLE client and executes change request from the client. 30 | */ 31 | class BLEComponentHandlerBase : private BLECharacteristicCallbacks { 32 | public: 33 | BLEComponentHandlerBase(EntityBase* component, const BLECharacteristicInfoForHandler& characteristic_info); 34 | virtual ~BLEComponentHandlerBase(); 35 | 36 | void setup(BLEServer* ble_server); 37 | 38 | virtual void send_value(float value); 39 | virtual void send_value(string value); 40 | virtual void send_value(bool value); 41 | 42 | protected: 43 | virtual EntityBase* get_component() { return component; } 44 | virtual string get_component_description() { return get_component()->get_name(); } 45 | BLECharacteristic* get_characteristic() { return characteristic; } 46 | 47 | virtual bool can_receive_writes() { return false; } 48 | virtual void on_characteristic_written() {} 49 | 50 | bool is_security_enabled(); 51 | 52 | private: 53 | virtual void onWrite(BLECharacteristic *characteristic); // inherited from BLECharacteristicCallbacks 54 | 55 | EntityBase* component; 56 | BLECharacteristicInfoForHandler characteristic_info; 57 | 58 | BLECharacteristic* characteristic; 59 | }; 60 | 61 | } // namespace esp32_ble_controller 62 | } // namespace esphome 63 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_component_handler_factory.cpp: -------------------------------------------------------------------------------- 1 | #include "ble_component_handler_factory.h" 2 | 3 | #include "ble_component_handler.h" 4 | #include "ble_fan_handler.h" 5 | #include "ble_sensor_handler.h" 6 | #include "ble_switch_handler.h" 7 | 8 | namespace esphome { 9 | namespace esp32_ble_controller { 10 | 11 | static const char *TAG = "ble_component_handler_factory"; 12 | 13 | BLEComponentHandlerBase* BLEComponentHandlerFactory::create_component_handler(EntityBase* component, const BLECharacteristicInfoForHandler& characteristic_info) { 14 | return new BLEComponentHandler(component, characteristic_info); 15 | } 16 | 17 | #ifdef USE_BINARY_SENSOR 18 | BLEComponentHandlerBase* esphome::esp32_ble_controller::BLEComponentHandlerFactory::create_binary_sensor_handler(binary_sensor::BinarySensor* component, const BLECharacteristicInfoForHandler& characteristic_info) { 19 | return create_component_handler(component, characteristic_info); 20 | } 21 | #endif 22 | 23 | #ifdef USE_COVER 24 | #endif 25 | 26 | #ifdef USE_FAN 27 | BLEComponentHandlerBase* BLEComponentHandlerFactory::BLEComponentHandlerFactory::create_fan_handler(fan::Fan* component, const BLECharacteristicInfoForHandler& characteristic_info) { 28 | return new BLEFanHandler(component, characteristic_info); 29 | } 30 | #endif 31 | 32 | #ifdef USE_LIGHT 33 | #endif 34 | 35 | #ifdef USE_SENSOR 36 | BLEComponentHandlerBase* esphome::esp32_ble_controller::BLEComponentHandlerFactory::create_sensor_handler(sensor::Sensor* component, const BLECharacteristicInfoForHandler& characteristic_info) { 37 | return new BLESensorHandler(component, characteristic_info); 38 | } 39 | #endif 40 | 41 | #ifdef USE_SWITCH 42 | BLEComponentHandlerBase* BLEComponentHandlerFactory::create_switch_handler(switch_::Switch* component, const BLECharacteristicInfoForHandler& characteristic_info) { 43 | return new BLESwitchHandler(component, characteristic_info); 44 | } 45 | #endif 46 | 47 | #ifdef USE_TEXT_SENSOR 48 | BLEComponentHandlerBase* esphome::esp32_ble_controller::BLEComponentHandlerFactory::create_text_sensor_handler(text_sensor::TextSensor* component, const BLECharacteristicInfoForHandler& characteristic_info) { 49 | return create_component_handler(component, characteristic_info); 50 | } 51 | #endif 52 | 53 | #ifdef USE_CLIMATE 54 | #endif 55 | 56 | } // namespace esp32_ble_controller 57 | } // namespace esphome 58 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_component_handler_factory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/defines.h" 4 | 5 | #include "ble_component_handler_base.h" 6 | 7 | #ifdef USE_BINARY_SENSOR 8 | #include "esphome/components/binary_sensor/binary_sensor.h" 9 | #endif 10 | #ifdef USE_COVER 11 | #endif 12 | #ifdef USE_FAN 13 | #include "esphome/components/fan/fan.h" 14 | #endif 15 | #ifdef USE_LIGHT 16 | #endif 17 | #ifdef USE_SENSOR 18 | #include "esphome/components/sensor/sensor.h" 19 | #endif 20 | #ifdef USE_SWITCH 21 | #include "esphome/components/switch/switch.h" 22 | #endif 23 | #ifdef USE_TEXT_SENSOR 24 | #include "esphome/components/text_sensor/text_sensor.h" 25 | #endif 26 | #ifdef USE_CLIMATE 27 | #endif 28 | 29 | using std::string; 30 | 31 | namespace esphome { 32 | namespace esp32_ble_controller { 33 | 34 | /** 35 | * Factory to create a specific BLEComponentHandlerBase instance for a component (sensor, switch, ...) and a given characteristic info structure. 36 | * Typically the factory will create a hanlder derived from BLEComponentHandlerBase that is specific to the component, like BLESwitchHandler for switches, which allows turning the switch on and off. 37 | */ 38 | class BLEComponentHandlerFactory { 39 | public: 40 | static BLEComponentHandlerBase* create_component_handler(EntityBase* component, const BLECharacteristicInfoForHandler& characteristic_info); 41 | 42 | #ifdef USE_BINARY_SENSOR 43 | static BLEComponentHandlerBase* create_binary_sensor_handler(binary_sensor::BinarySensor* component, const BLECharacteristicInfoForHandler& characteristic_info); 44 | #endif 45 | 46 | #ifdef USE_COVER 47 | #endif 48 | 49 | #ifdef USE_FAN 50 | static BLEComponentHandlerBase* create_fan_handler(fan::Fan* component, const BLECharacteristicInfoForHandler& characteristic_info); 51 | #endif 52 | 53 | #ifdef USE_LIGHT 54 | #endif 55 | 56 | #ifdef USE_SENSOR 57 | static BLEComponentHandlerBase* create_sensor_handler(sensor::Sensor* component, const BLECharacteristicInfoForHandler& characteristic_info); 58 | #endif 59 | 60 | #ifdef USE_SWITCH 61 | static BLEComponentHandlerBase* create_switch_handler(switch_::Switch* component, const BLECharacteristicInfoForHandler& characteristic_info); 62 | #endif 63 | 64 | #ifdef USE_TEXT_SENSOR 65 | static BLEComponentHandlerBase* create_text_sensor_handler(text_sensor::TextSensor* component, const BLECharacteristicInfoForHandler& characteristic_info); 66 | #endif 67 | 68 | #ifdef USE_CLIMATE 69 | #endif 70 | }; 71 | 72 | } // namespace esp32_ble_controller 73 | } // namespace esphome 74 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_fan_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "ble_fan_handler.h" 2 | 3 | #ifdef USE_FAN 4 | 5 | #include "ble_utils.h" 6 | 7 | namespace esphome { 8 | namespace esp32_ble_controller { 9 | 10 | static const char *TAG = "ble_fan_handler"; 11 | 12 | static const char *OPT_FAN_ON = "on"; 13 | static const char *OPT_FAN_OFF = "off"; 14 | 15 | static const char *OPT_OSCILLATING_YES = "yes"; 16 | static const char *OPT_OSCILLATING_NO = "no"; 17 | 18 | static const char *OPT_DIRECTION_FWD = "forward"; 19 | static const char *OPT_DIRECTION_REV = "reverse"; 20 | 21 | void BLEFanHandler::send_value(bool on_off) { 22 | string state_as_string; 23 | 24 | state_as_string = "fan="; 25 | state_as_string += on_off ? OPT_FAN_ON : OPT_FAN_OFF; 26 | 27 | /*const*/ Fan* fan = get_component(); 28 | const auto& traits = fan->get_traits(); 29 | 30 | if (traits.supports_speed()) { 31 | state_as_string += " speed="; 32 | state_as_string += to_string(fan->speed); 33 | const int max_speed = traits.supported_speed_count(); 34 | if (max_speed != 100) { 35 | state_as_string += "/"; 36 | state_as_string += to_string(max_speed); 37 | } 38 | } 39 | 40 | if (traits.supports_oscillation()) { 41 | state_as_string += " oscillating="; 42 | state_as_string += fan->oscillating ? OPT_OSCILLATING_YES : OPT_OSCILLATING_NO; 43 | } 44 | 45 | if (traits.supports_direction()) { 46 | state_as_string += " direction="; 47 | state_as_string += fan->direction == fan::FanDirection::FORWARD ? OPT_DIRECTION_FWD : OPT_DIRECTION_REV; 48 | } 49 | 50 | BLEComponentHandlerBase::send_value(state_as_string); 51 | } 52 | 53 | void BLEFanHandler::on_characteristic_written() { 54 | std::string value = get_characteristic()->getValue(); 55 | 56 | Fan* fan = get_component(); 57 | 58 | // for backward compatibility 59 | if (value.length() == 1) { 60 | uint8_t on = value[0]; 61 | ESP_LOGD(TAG, "Fan chracteristic written: %d", on); 62 | if (on) 63 | fan->turn_on().perform(); 64 | else 65 | fan->turn_off().perform(); 66 | return; 67 | } 68 | 69 | auto call = fan->make_call(); 70 | const auto& traits = fan->get_traits(); 71 | for (const string& option : split(value)) { 72 | if (option == OPT_FAN_ON) { 73 | call.set_state(true); 74 | continue; 75 | } 76 | if (option == OPT_FAN_OFF) { 77 | call.set_state(false); 78 | continue; 79 | } 80 | 81 | if (traits.supports_speed()) { 82 | const optional opt_speed = parse_number(option); 83 | if (opt_speed.has_value()) { 84 | const int speed = opt_speed.value(); 85 | if (speed >= 0 && speed <= traits.supported_speed_count()) { 86 | call.set_speed(speed); 87 | continue; 88 | } 89 | } 90 | } 91 | 92 | if (traits.supports_oscillation()) { 93 | if (option == OPT_OSCILLATING_YES) { 94 | call.set_oscillating(true); 95 | continue; 96 | } 97 | if (option == OPT_OSCILLATING_NO) { 98 | call.set_oscillating(false); 99 | continue; 100 | } 101 | } 102 | 103 | if (traits.supports_direction()) { 104 | if (option == OPT_DIRECTION_FWD) { 105 | call.set_direction(fan::FanDirection::FORWARD); 106 | continue; 107 | } 108 | if (option == OPT_DIRECTION_REV) { 109 | call.set_direction(fan::FanDirection::REVERSE); 110 | continue; 111 | } 112 | } 113 | 114 | ESP_LOGW(TAG, "Unknown fan option: %s", option.c_str()); 115 | } 116 | call.perform(); 117 | } 118 | 119 | } // namespace esp32_ble_controller 120 | } // namespace esphome 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_fan_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/defines.h" 4 | #ifdef USE_FAN 5 | 6 | #include 7 | 8 | #include "esphome/components/fan/fan.h" 9 | 10 | #include "ble_component_handler.h" 11 | 12 | using std::string; 13 | 14 | namespace esphome { 15 | namespace esp32_ble_controller { 16 | 17 | using fan::Fan; 18 | 19 | /** 20 | * Special component handler for fans, which allows turning the fan on and off from a BLE client. 21 | */ 22 | class BLEFanHandler : public BLEComponentHandler { 23 | public: 24 | BLEFanHandler(Fan* component, const BLECharacteristicInfoForHandler& characteristic_info) : BLEComponentHandler(component, characteristic_info) {} 25 | virtual ~BLEFanHandler() {} 26 | 27 | virtual void send_value(bool value) override; 28 | 29 | protected: 30 | virtual bool can_receive_writes() { return true; } 31 | virtual void on_characteristic_written() override; 32 | }; 33 | 34 | } // namespace esp32_ble_controller 35 | } // namespace esphome 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_maintenance_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ble_maintenance_handler.h" 4 | 5 | #include "esphome/core/log.h" 6 | #include "esphome/core/application.h" 7 | #ifdef USE_LOGGER 8 | #include "esphome/components/logger/logger.h" 9 | #endif 10 | 11 | #include "esp32_ble_controller.h" 12 | #include "ble_command.h" 13 | #include "automation.h" 14 | #include "ble_utils.h" 15 | 16 | // https://www.uuidgenerator.net 17 | #define SERVICE_UUID "7b691dff-9062-4192-b46a-692e0da81d91" 18 | #define CHARACTERISTIC_UUID_CMD "1d3c6498-cfdf-44a1-9038-3e757dcc449d" 19 | #define CHARACTERISTIC_UUID_LOGGING "a1083f3b-0ad6-49e0-8a9d-56eb5bf462ca" 20 | 21 | namespace esphome { 22 | namespace esp32_ble_controller { 23 | 24 | static const char *TAG = "ble_maintenance_handler"; 25 | 26 | BLEMaintenanceHandler::BLEMaintenanceHandler() : ble_command_characteristic(nullptr) { 27 | commands.push_back(new BLECommandHelp()); 28 | commands.push_back(new BLECommandSwitchMaintenanceOnOrOff()); 29 | commands.push_back(new BLECommandSwitchComponentServicesOnOrOff()); 30 | #ifdef USE_WIFI 31 | commands.push_back(new BLECommandWifiConfiguration()); 32 | #endif 33 | commands.push_back(new BLECommandPairings()); 34 | commands.push_back(new BLECommandVersion()); 35 | 36 | #ifdef USE_LOGGER 37 | log_level = ESPHOME_LOG_LEVEL; 38 | logging_characteristic = nullptr; 39 | 40 | commands.push_back(new BLECommandLogLevel()); 41 | #endif 42 | } 43 | 44 | void BLEMaintenanceHandler::setup(BLEServer* ble_server) { 45 | ESP_LOGCONFIG(TAG, "Setting up maintenance service"); 46 | 47 | BLEService* service = ble_server->createService(SERVICE_UUID); 48 | 49 | ble_command_characteristic = create_writeable_ble_characteristic(service, CHARACTERISTIC_UUID_CMD, this, "BLE Command Channel"); 50 | ble_command_characteristic->setValue("Send 'help' for help."); 51 | 52 | #ifdef USE_LOGGER 53 | logging_characteristic = create_read_only_ble_characteristic(service, CHARACTERISTIC_UUID_LOGGING, "Log messages"); 54 | #endif 55 | 56 | service->start(); 57 | 58 | #ifdef USE_LOGGER 59 | if (!global_ble_controller->get_component_services_exposed()) { 60 | log_level = ESPHOME_LOG_LEVEL_CONFIG; 61 | } 62 | 63 | // NOTE: We register the callback after the service has been started! 64 | if (logger::global_logger != nullptr) { 65 | logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { 66 | // publish log message 67 | this->send_log_message(level, tag, message); 68 | }); 69 | } 70 | #endif 71 | } 72 | 73 | void BLEMaintenanceHandler::onWrite(BLECharacteristic *characteristic) { 74 | if (characteristic == ble_command_characteristic) { 75 | global_ble_controller->execute_in_loop([this](){ on_command_written(); }); 76 | } else { 77 | ESP_LOGW(TAG, "Unknown characteristic written!"); 78 | } 79 | } 80 | 81 | void BLEMaintenanceHandler::on_command_written() { 82 | string command_line = ble_command_characteristic->getValue(); 83 | ESP_LOGD(TAG, "Received BLE command: %s", command_line.c_str()); 84 | vector tokens = split(command_line); 85 | if (!tokens.empty()) { 86 | string command_name = tokens[0]; 87 | for (const auto& command : get_commands()) { 88 | if (command->get_name() == command_name) { 89 | ESP_LOGI(TAG, "Executing BLE command: %s", command_name.c_str()); 90 | tokens.erase(tokens.begin()); 91 | command->execute(tokens); 92 | return; 93 | } 94 | } 95 | send_command_result("Unkown command '" + command_name + "', try 'help'."); 96 | } 97 | } 98 | 99 | void BLEMaintenanceHandler::send_command_result(const string& result_message) { 100 | if (ble_command_characteristic != nullptr) { 101 | global_ble_controller->execute_in_loop([this, result_message] { 102 | ble_command_characteristic->setValue(result_message); 103 | }); 104 | } 105 | 106 | // global_ble_controller->execute_in_loop([this, result_message] { 107 | // const uint32_t delay_millis = 50; 108 | // App.scheduler.set_timeout(global_ble_controller, "command_result", delay_millis, [this, result_message]{ ble_command_characteristic->setValue(result_message); }); 109 | // }); 110 | } 111 | 112 | bool BLEMaintenanceHandler::is_security_enabled() { 113 | return global_ble_controller->get_security_enabled(); 114 | } 115 | 116 | #ifdef USE_LOGGER 117 | /** 118 | * Removes magic logger symbols from the message, e.g., sequences that mark the start or the end, or a color. 119 | */ 120 | string remove_logger_magic(const string& message) { 121 | // Note: We do not use regex replacement because it enlarges the binary by roughly 50kb! 122 | string result; 123 | bool within_magic = false; 124 | for (string::size_type i = 0; i < message.length() - 1; ++i) { 125 | if (message[i] == '\033' && message[i+1] == '[') { // log magic always starts with "\033[" see log.h 126 | within_magic = true; 127 | ++i; 128 | } else if (within_magic) { 129 | within_magic = (message[i] != 'm'); 130 | } else { 131 | result.push_back(message[i]); 132 | } 133 | } 134 | return result; 135 | } 136 | 137 | void BLEMaintenanceHandler::send_log_message(int level, const char *tag, const char *message) { 138 | if (logging_characteristic != nullptr && level <= this->log_level) { 139 | logging_characteristic->setValue(remove_logger_magic(message)); 140 | logging_characteristic->notify(); 141 | } 142 | } 143 | #endif 144 | 145 | } // namespace esp32_ble_controller 146 | } // namespace esphome 147 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_maintenance_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "esphome/core/defines.h" 10 | 11 | using std::string; 12 | using std::vector; 13 | 14 | namespace esphome { 15 | namespace esp32_ble_controller { 16 | 17 | class BLECommand; 18 | class BLEControllerCustomCommandExecutionTrigger; 19 | 20 | /** 21 | * Provides standard maintenance support for the BLE controller like logging over BLE and controlling BLE mode. 22 | * It does not control individual ESPHome components (like sensors, switches, ...), but rather provides generic global functionality. 23 | * It provides a special BLE service with its own characteristics. 24 | * @brief Provides maintenance support for BLE clients (like controlling the BLE mode and logging over BLE) 25 | */ 26 | class BLEMaintenanceHandler : private BLECharacteristicCallbacks { 27 | public: 28 | BLEMaintenanceHandler(); 29 | virtual ~BLEMaintenanceHandler() {} 30 | 31 | void setup(BLEServer* ble_server); 32 | 33 | void add_command(BLECommand* command) { commands.push_back(command); } 34 | const vector& get_commands() const { return commands; } 35 | void send_command_result(const string& result_message); 36 | 37 | #ifdef USE_LOGGER 38 | int get_log_level() { return log_level; } 39 | void set_log_level(int level) { log_level = level; } 40 | 41 | void send_log_message(int level, const char *tag, const char *message); 42 | #endif 43 | 44 | private: 45 | virtual void onWrite(BLECharacteristic *characteristic) override; 46 | void on_command_written(); 47 | 48 | bool is_security_enabled(); 49 | 50 | private: 51 | BLEService* maintenance_service; 52 | 53 | BLECharacteristic* ble_command_characteristic; 54 | vector commands; 55 | 56 | #ifdef USE_LOGGER 57 | int log_level; 58 | 59 | BLECharacteristic* logging_characteristic; 60 | #endif 61 | }; 62 | 63 | } // namespace esp32_ble_controller 64 | } // namespace esphome 65 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_sensor_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "ble_sensor_handler.h" 2 | 3 | #ifdef USE_SENSOR 4 | 5 | namespace esphome { 6 | namespace esp32_ble_controller { 7 | 8 | static const char *TAG = "ble_sensor_handler"; 9 | 10 | string BLESensorHandler::get_component_description() { 11 | string uom = get_component()->get_unit_of_measurement(); 12 | if (uom.empty()) { 13 | return BLEComponentHandler::get_component_description(); 14 | } else { 15 | return BLEComponentHandler::get_component_description() + " (" + uom + ")"; 16 | } 17 | } 18 | 19 | } // namespace esp32_ble_controller 20 | } // namespace esphome 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_sensor_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/defines.h" 4 | #ifdef USE_SENSOR 5 | 6 | #include 7 | 8 | #include "esphome/components/sensor/sensor.h" 9 | 10 | #include "ble_component_handler.h" 11 | 12 | using std::string; 13 | 14 | namespace esphome { 15 | namespace esp32_ble_controller { 16 | 17 | using sensor::Sensor; 18 | 19 | /** 20 | * Special component handler for sensors, which adds the sensor's unit of measure to the component description. 21 | */ 22 | class BLESensorHandler : public BLEComponentHandler { 23 | public: 24 | BLESensorHandler(Sensor* component, const BLECharacteristicInfoForHandler& characteristic_info) : BLEComponentHandler(component, characteristic_info) {} 25 | virtual ~BLESensorHandler() {} 26 | 27 | protected: 28 | virtual string get_component_description(); 29 | }; 30 | 31 | } // namespace esp32_ble_controller 32 | } // namespace esphome 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_switch_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "ble_switch_handler.h" 2 | 3 | #ifdef USE_SWITCH 4 | 5 | #include "esphome/core/log.h" 6 | 7 | namespace esphome { 8 | namespace esp32_ble_controller { 9 | 10 | static const char *TAG = "ble_switch_handler"; 11 | 12 | // BLESwitchHandler::BLESwitchHandler(Switch* component, const BLECharacteristicInfoForHandler& characteristic_info) : BLEComponentHandler(component, characteristic_info) 13 | // { 14 | // set_value(component->state); // do not send yet! 15 | // } 16 | 17 | void BLESwitchHandler::on_characteristic_written() { 18 | std::string value = get_characteristic()->getValue(); 19 | if (value.length() == 1) { 20 | uint8_t on = value[0]; 21 | ESP_LOGD(TAG, "Switch chracteristic written: %d", on); 22 | if (on) 23 | get_component()->turn_on(); 24 | else 25 | get_component()->turn_off(); 26 | } 27 | } 28 | 29 | } // namespace esp32_ble_controller 30 | } // namespace esphome 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_switch_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/defines.h" 4 | #ifdef USE_SWITCH 5 | 6 | #include 7 | 8 | #include "esphome/components/switch/switch.h" 9 | 10 | #include "ble_component_handler.h" 11 | 12 | using std::string; 13 | 14 | namespace esphome { 15 | namespace esp32_ble_controller { 16 | 17 | using switch_::Switch; 18 | 19 | /** 20 | * Special component handler for switches, which allows turning the switch on and off from a BLE client. 21 | */ 22 | class BLESwitchHandler : public BLEComponentHandler { 23 | public: 24 | BLESwitchHandler(Switch* component, const BLECharacteristicInfoForHandler& characteristic_info) : BLEComponentHandler(component, characteristic_info) {} 25 | virtual ~BLESwitchHandler() {} 26 | 27 | protected: 28 | virtual bool can_receive_writes() { return true; } 29 | virtual void on_characteristic_written() override; 30 | }; 31 | 32 | } // namespace esp32_ble_controller 33 | } // namespace esphome 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "ble_utils.h" 2 | 3 | #include 4 | 5 | #include "esphome/core/log.h" 6 | 7 | #include "esp32_ble_controller.h" 8 | 9 | namespace esphome { 10 | namespace esp32_ble_controller { 11 | 12 | static const char *TAG = "ble_utils"; 13 | 14 | vector get_bonded_devices() { 15 | vector paired_devices; 16 | 17 | int dev_num = esp_ble_get_bond_device_num(); 18 | 19 | esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t*) malloc(sizeof(esp_ble_bond_dev_t) * dev_num); 20 | esp_ble_get_bond_device_list(&dev_num, dev_list); 21 | 22 | for (int i = 0; i < dev_num; i++) { 23 | char bd_address_str[18]; 24 | esp_bd_addr_t& bd_address = dev_list[i].bd_addr; 25 | snprintf(bd_address_str, sizeof(bd_address_str), "%X:%X:%X:%X:%X:%X", bd_address[0], bd_address[1], bd_address[2], bd_address[3], bd_address[4], bd_address[5]); 26 | paired_devices.push_back(bd_address_str); 27 | } 28 | 29 | free(dev_list); 30 | 31 | return paired_devices; 32 | } 33 | 34 | void remove_all_bonded_devices() 35 | { 36 | int dev_num = esp_ble_get_bond_device_num(); 37 | 38 | esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t*) malloc(sizeof(esp_ble_bond_dev_t) * dev_num); 39 | esp_ble_get_bond_device_list(&dev_num, dev_list); 40 | for (int i = 0; i < dev_num; i++) { 41 | esp_ble_remove_bond_device(dev_list[i].bd_addr); 42 | } 43 | 44 | free(dev_list); 45 | } 46 | 47 | BLECharacteristic* create_ble_characteristic(BLEService* service, const string& characteristic_uuid, uint32_t properties, BLECharacteristicCallbacks* callbacks, const string& description, bool with2902) { 48 | BLECharacteristic* characteristic = service->createCharacteristic(characteristic_uuid, properties); 49 | 50 | // Set access permissions. 51 | esp_gatt_perm_t access_permissions; 52 | if (global_ble_controller->get_security_enabled()) { 53 | access_permissions = ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_WRITE_ENC_MITM; // signing (ESP_GATT_PERM_WRITE_SIGNED_MITM) did not work with iPhone 54 | } else { 55 | access_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; 56 | } 57 | characteristic->setAccessPermissions(access_permissions); 58 | 59 | // Add a 2901 descriptor to the characteristic, which sets a user-friendly description. 60 | BLEDescriptor* descriptor_2901 = new BLEDescriptor(BLEUUID((uint16_t)0x2901)); 61 | descriptor_2901->setAccessPermissions(access_permissions); 62 | descriptor_2901->setValue(description); 63 | characteristic->addDescriptor(descriptor_2901); 64 | 65 | // If requested, add a 2902 descriptor to the characteristic, which lets the client control if it wants to receive new values (and notifications) for this characteristic. 66 | if (with2902) { 67 | // With this descriptor clients can switch notifications on and off, but we want to send notifications anyway as long as we are connected. The homebridge plug-in cannot turn notifications on and off. 68 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml 69 | BLEDescriptor* descriptor_2902 = new BLE2902(); 70 | descriptor_2902->setAccessPermissions(access_permissions); 71 | characteristic->addDescriptor(descriptor_2902); 72 | } 73 | 74 | if (callbacks != nullptr) { 75 | characteristic->setCallbacks(callbacks); 76 | } 77 | 78 | return characteristic; 79 | } 80 | 81 | BLECharacteristic* create_read_only_ble_characteristic(BLEService* service, const string& characteristic_uuid, const string& description, bool with2902) { 82 | uint32_t properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY; 83 | return create_ble_characteristic(service, characteristic_uuid, properties, nullptr, description, with2902); 84 | } 85 | 86 | BLECharacteristic* create_writeable_ble_characteristic(BLEService* service, const string& characteristic_uuid, BLECharacteristicCallbacks* callbacks, const string& description, bool with2902) { 87 | uint32_t properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE; 88 | return create_ble_characteristic(service, characteristic_uuid, properties, callbacks, description, with2902); 89 | } 90 | 91 | vector split(string text, char delimiter) { 92 | vector result; 93 | int j = 0; 94 | for (int i = 0; i < text.length(); i ++) { 95 | if (text[i] == delimiter) { 96 | string token = text.substr(j, i - j); 97 | if (token.length()) { 98 | result.push_back(token); 99 | } 100 | j = i + 1; 101 | } 102 | } 103 | if (j < text.length()) { 104 | result.push_back(text.substr(j)); 105 | } 106 | return result; 107 | } 108 | 109 | } // namespace esp32_ble_controller 110 | } // namespace esphome 111 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/ble_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using std::string; 9 | using std::vector; 10 | 11 | namespace esphome { 12 | namespace esp32_ble_controller { 13 | 14 | vector get_bonded_devices(); 15 | void remove_all_bonded_devices(); 16 | 17 | BLECharacteristic* create_read_only_ble_characteristic(BLEService* service, const string& characteristic_uuid, const string& description, bool with2902 = true); 18 | 19 | BLECharacteristic* create_writeable_ble_characteristic(BLEService* service, const string& characteristic_uuid, BLECharacteristicCallbacks* callbacks, const string& description, bool with2902 = true); 20 | 21 | vector split(string text, char delimiter = ' '); 22 | 23 | } // namespace esp32_ble_controller 24 | } // namespace esphome 25 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/esp32_ble_controller.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "esphome/core/application.h" 6 | #include "esphome/core/log.h" 7 | 8 | #include 9 | #include 10 | 11 | #include "esp32_ble_controller.h" 12 | 13 | #include "ble_maintenance_handler.h" 14 | #include "ble_utils.h" 15 | #include "ble_command.h" 16 | #include "automation.h" 17 | #include "ble_component_handler_factory.h" 18 | 19 | namespace esphome { 20 | namespace esp32_ble_controller { 21 | 22 | static const char *TAG = "esp32_ble_controller"; 23 | 24 | ESP32BLEController::ESP32BLEController() : maintenance_handler(new BLEMaintenanceHandler()) {} 25 | 26 | /// pre-setup configuration /////////////////////////////////////////////////////////////////////////////////////////////////////////////// 27 | 28 | void ESP32BLEController::register_component(EntityBase* component, const string& serviceUUID, const string& characteristic_UUID, bool use_BLE2902) { 29 | BLECharacteristicInfoForHandler info; 30 | info.service_UUID = serviceUUID; 31 | info.characteristic_UUID = characteristic_UUID; 32 | info.use_BLE2902 = use_BLE2902; 33 | 34 | info_for_component[component->get_object_id()] = info; 35 | } 36 | 37 | void ESP32BLEController::ESP32BLEController::register_command(const string& name, const string& description, BLEControllerCustomCommandExecutionTrigger* trigger) { 38 | maintenance_handler->add_command(new BLECustomCommand(name, description, trigger)); 39 | } 40 | 41 | const vector& ESP32BLEController::get_commands() const { 42 | return maintenance_handler->get_commands(); 43 | } 44 | 45 | void ESP32BLEController::add_on_show_pass_key_callback(std::function&& trigger_function) { 46 | can_show_pass_key = true; 47 | on_show_pass_key_callbacks.add(std::move(trigger_function)); 48 | } 49 | 50 | void ESP32BLEController::add_on_authentication_complete_callback(std::function&& trigger_function) { 51 | on_authentication_complete_callbacks.add(std::move(trigger_function)); 52 | } 53 | 54 | void ESP32BLEController::add_on_connected_callback(std::function&& trigger_function) { 55 | on_connected_callbacks.add(std::move(trigger_function)); 56 | } 57 | 58 | void ESP32BLEController::add_on_disconnected_callback(std::function&& trigger_function) { 59 | on_disconnected_callbacks.add(std::move(trigger_function)); 60 | } 61 | 62 | BLEMaintenanceMode set_feature(BLEMaintenanceMode current_mode, BLEMaintenanceMode feature, bool is_set) { 63 | uint8_t new_mode = static_cast(current_mode) & (~static_cast(feature)); 64 | if (is_set) { 65 | new_mode |= static_cast(feature); 66 | } 67 | return static_cast(new_mode); 68 | } 69 | 70 | void ESP32BLEController::set_maintenance_service_exposed_after_flash(bool exposed) { 71 | initial_ble_mode_after_flashing = set_feature(initial_ble_mode_after_flashing, BLEMaintenanceMode::MAINTENANCE_SERVICE, exposed); 72 | } 73 | 74 | void ESP32BLEController::set_security_enabled(bool enabled) { 75 | set_security_mode(BLESecurityMode::SECURE); 76 | } 77 | 78 | /// setup /////////////////////////////////////////////////////////////////////////////////////////////////////////////// 79 | 80 | void ESP32BLEController::setup() { 81 | ESP_LOGCONFIG(TAG, "Setting up BLE controller ..."); 82 | 83 | initialize_ble_mode(); 84 | 85 | if (ble_mode == BLEMaintenanceMode::NONE) { 86 | ESP_LOGCONFIG(TAG, "BLE inactive"); 87 | return; 88 | } 89 | 90 | #ifdef USE_WIFI 91 | wifi_configuration_handler.setup(); 92 | #endif 93 | 94 | if (!setup_ble()) { 95 | return; 96 | } 97 | 98 | if (global_ble_controller == nullptr) { 99 | global_ble_controller = this; 100 | } else { 101 | ESP_LOGE(TAG, "Already have an instance of the BLE controller"); 102 | } 103 | 104 | // Create the BLE Device 105 | BLEDevice::init(App.get_name()); 106 | 107 | configure_ble_security(); 108 | 109 | setup_ble_server_and_services(); 110 | 111 | // Start advertising 112 | // BLEAdvertising* advertising = BLEDevice::getAdvertising(); 113 | // advertising->setMinInterval(0x800); // suggested default: 1.28s 114 | // advertising->setMaxInterval(0x800); 115 | // advertising->setMinPreferred(80); // = 100 ms, see https://www.novelbits.io/ble-connection-intervals/, https://www.novelbits.io/bluetooth-low-energy-advertisements-part-1/ 116 | // advertising->setMaxPreferred(800); // = 1000 ms 117 | BLEDevice::startAdvertising(); 118 | } 119 | 120 | bool ESP32BLEController::setup_ble() { 121 | if (btStarted()) { 122 | ESP_LOGI(TAG, "BLE already started"); 123 | return true; 124 | } 125 | 126 | ESP_LOGI(TAG, " Setting up BLE ..."); 127 | 128 | esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); 129 | 130 | // Initialize the bluetooth controller with the default configuration 131 | if (!btStart()) { 132 | ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); 133 | mark_failed(); 134 | return false; 135 | } 136 | 137 | esp_err_t err = esp_bluedroid_init(); 138 | if (err != ESP_OK) { 139 | ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err); 140 | mark_failed(); 141 | return false; 142 | } 143 | 144 | err = esp_bluedroid_enable(); 145 | if (err != ESP_OK) { 146 | ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err); 147 | mark_failed(); 148 | return false; 149 | } 150 | 151 | return true; 152 | } 153 | 154 | void ESP32BLEController::setup_ble_server_and_services() { 155 | ble_server = BLEDevice::createServer(); 156 | ble_server->setCallbacks(this); 157 | 158 | if (get_maintenance_service_exposed()) { 159 | maintenance_handler->setup(ble_server); 160 | } 161 | 162 | if (get_component_services_exposed()) { 163 | setup_ble_services_for_components(); 164 | } 165 | } 166 | 167 | void ESP32BLEController::setup_ble_services_for_components() { 168 | #ifdef USE_BINARY_SENSOR 169 | setup_ble_services_for_components(App.get_binary_sensors(), BLEComponentHandlerFactory::create_binary_sensor_handler); 170 | #endif 171 | #ifdef USE_COVER 172 | //setup_ble_services_for_components(App.get_covers()); 173 | #endif 174 | #ifdef USE_FAN 175 | setup_ble_services_for_components(App.get_fans(), BLEComponentHandlerFactory::create_fan_handler); 176 | #endif 177 | #ifdef USE_LIGHT 178 | //setup_ble_services_for_components(App.get_lights()); 179 | #endif 180 | #ifdef USE_SENSOR 181 | setup_ble_services_for_components(App.get_sensors(), BLEComponentHandlerFactory::create_sensor_handler); 182 | #endif 183 | #ifdef USE_SWITCH 184 | setup_ble_services_for_components(App.get_switches(), BLEComponentHandlerFactory::create_switch_handler); 185 | #endif 186 | #ifdef USE_TEXT_SENSOR 187 | setup_ble_services_for_components(App.get_text_sensors(), BLEComponentHandlerFactory::create_text_sensor_handler); 188 | #endif 189 | #ifdef USE_CLIMATE 190 | //setup_ble_services_for_components(App.get_climates()); 191 | #endif 192 | 193 | for (auto const& entry : handler_for_component) { 194 | auto* handler = entry.second; 195 | handler->setup(ble_server); 196 | } 197 | 198 | register_state_change_callbacks_and_send_initial_states(); 199 | } 200 | 201 | template 202 | void ESP32BLEController::setup_ble_services_for_components(const vector& components, BLEComponentHandlerBase* (*handler_creator)(C*, const BLECharacteristicInfoForHandler&)) { 203 | for (C* component: components) { 204 | setup_ble_service_for_component(component, handler_creator); 205 | } 206 | } 207 | 208 | template 209 | void ESP32BLEController::setup_ble_service_for_component(C* component, BLEComponentHandlerBase* (*handler_creator)(C*, const BLECharacteristicInfoForHandler&)) { 210 | static_assert(std::is_base_of::value, "EntityBase subclasses expected"); 211 | 212 | auto object_id = component->get_object_id(); 213 | if (info_for_component.count(object_id)) { 214 | auto info = info_for_component[object_id]; 215 | handler_for_component[object_id] = handler_creator(component, info); 216 | } 217 | } 218 | 219 | void ESP32BLEController::register_state_change_callbacks_and_send_initial_states() { 220 | #ifdef USE_BINARY_SENSOR 221 | for (auto *obj : App.get_binary_sensors()) { 222 | if (info_for_component.count(obj->get_object_id())) { 223 | obj->add_on_state_callback([this, obj](bool state) { this->on_binary_sensor_update(obj, state); }); 224 | if (obj->has_state()) 225 | update_component_state(obj, obj->state); 226 | } 227 | } 228 | #endif 229 | #ifdef USE_CLIMATE 230 | // for (auto *obj : App.get_climates()) { 231 | // if (info_for_component.count(obj->get_object_id())) 232 | // obj->add_on_state_callback([this, obj]() { this->on_climate_update(obj); }); 233 | // } 234 | #endif 235 | #ifdef USE_COVER 236 | // for (auto *obj : App.get_covers()) { 237 | // if (info_for_component.count(obj->get_object_id())) 238 | // obj->add_on_state_callback([this, obj]() { this->on_cover_update(obj); }); 239 | // } 240 | #endif 241 | #ifdef USE_FAN 242 | for (auto *obj : App.get_fans()) { 243 | if (info_for_component.count(obj->get_object_id())) { 244 | obj->add_on_state_callback([this, obj]() { this->on_fan_update(obj); }); 245 | update_component_state(obj, obj->state); 246 | } 247 | } 248 | #endif 249 | #ifdef USE_LIGHT 250 | // for (auto *obj : App.get_lights()) { 251 | // if (info_for_component.count(obj->get_object_id())) 252 | // obj->add_new_remote_values_callback([this, obj]() { this->on_light_update(obj); }); 253 | // } 254 | #endif 255 | #ifdef USE_SENSOR 256 | for (auto *obj : App.get_sensors()) { 257 | if (info_for_component.count(obj->get_object_id())) { 258 | obj->add_on_state_callback([this, obj](float state) { this->on_sensor_update(obj, state); }); 259 | if (obj->has_state()) 260 | update_component_state(obj, obj->state); 261 | } 262 | } 263 | #endif 264 | #ifdef USE_SWITCH 265 | for (auto *obj : App.get_switches()) { 266 | if (info_for_component.count(obj->get_object_id())) { 267 | obj->add_on_state_callback([this, obj](bool state) { this->on_switch_update(obj, state); }); 268 | update_component_state(obj, obj->state); 269 | } 270 | } 271 | #endif 272 | #ifdef USE_TEXT_SENSOR 273 | for (auto *obj : App.get_text_sensors()) { 274 | if (info_for_component.count(obj->get_object_id())) { 275 | obj->add_on_state_callback([this, obj](std::string state) { this->on_text_sensor_update(obj, state); }); 276 | if (obj->has_state()) 277 | update_component_state(obj, obj->state); 278 | } 279 | } 280 | #endif 281 | } 282 | 283 | void ESP32BLEController::initialize_ble_mode() { 284 | // Note: We include the compilation time to force a reset after flashing new firmware 285 | ble_mode_preference = global_preferences->make_preference(fnv1_hash("ble-mode#" + App.get_compilation_time())); 286 | 287 | if (!ble_mode_preference.load(&ble_mode)) { 288 | ble_mode = initial_ble_mode_after_flashing; 289 | } 290 | 291 | ESP_LOGCONFIG(TAG, "BLE mode: %d", static_cast(ble_mode)); 292 | } 293 | 294 | void ESP32BLEController::switch_ble_mode(BLEMaintenanceMode newMode) { 295 | if (ble_mode != newMode) { 296 | ESP_LOGI(TAG, "Switching BLE mode to %d and rebooting", static_cast(newMode)); 297 | 298 | ble_mode = newMode; 299 | ble_mode_preference.save(&ble_mode); 300 | 301 | App.safe_reboot(); 302 | } 303 | } 304 | 305 | void ESP32BLEController::switch_maintenance_service_exposed(bool exposed) { 306 | switch_ble_mode(set_feature(ble_mode, BLEMaintenanceMode::MAINTENANCE_SERVICE, exposed)); 307 | } 308 | 309 | void ESP32BLEController::switch_component_services_exposed(bool exposed) { 310 | switch_ble_mode(set_feature(ble_mode, BLEMaintenanceMode::COMPONENT_SERVICES, exposed)); 311 | } 312 | 313 | void ESP32BLEController::dump_config() { 314 | if (ble_mode == BLEMaintenanceMode::NONE) { 315 | return; 316 | } 317 | 318 | ESP_LOGCONFIG(TAG, "Bluetooth Low Energy Controller:"); 319 | ESP_LOGCONFIG(TAG, " BLE device address: %s", BLEDevice::getAddress().toString().c_str()); 320 | ESP_LOGCONFIG(TAG, " BLE mode: %d", (uint8_t) ble_mode); 321 | 322 | if (get_security_mode() != BLESecurityMode::NONE) { 323 | if (get_security_mode() == BLESecurityMode::BOND) { 324 | ESP_LOGCONFIG(TAG, " only bonding enabled, no real security"); 325 | } else { 326 | ESP_LOGCONFIG(TAG, " security enabled (secure connections, MITM protection)"); 327 | } 328 | 329 | vector bonded_devices = get_bonded_devices(); 330 | if (bonded_devices.empty()) { 331 | ESP_LOGCONFIG(TAG, " no bonded BLE devices"); 332 | } else { 333 | ESP_LOGCONFIG(TAG, " bonded BLE devices (%d):", bonded_devices.size()); 334 | int i = 0; 335 | for (const auto& bd_address : bonded_devices) { 336 | ESP_LOGCONFIG(TAG, " %d) BD address %s", ++i, bd_address.c_str()); 337 | } 338 | } 339 | } else { 340 | ESP_LOGCONFIG(TAG, " security disabled"); 341 | } 342 | } 343 | 344 | /// run /////////////////////////////////////////////////////////////////////////////////////////////////////////////// 345 | 346 | #ifdef USE_WIFI 347 | void ESP32BLEController::ESP32BLEController::set_wifi_configuration(const string& ssid, const string& password, bool hidden_network) { 348 | wifi_configuration_handler.set_credentials(ssid, password, hidden_network); 349 | } 350 | 351 | void ESP32BLEController::ESP32BLEController::clear_wifi_configuration_and_reboot() { 352 | wifi_configuration_handler.clear_credentials(); 353 | 354 | App.safe_reboot(); 355 | } 356 | 357 | const optional ESP32BLEController::ESP32BLEController::get_current_ssid_in_wifi_configuration() { 358 | return wifi_configuration_handler.get_current_ssid(); 359 | } 360 | #endif 361 | 362 | void ESP32BLEController::send_command_result(const string& result_message) { 363 | maintenance_handler->send_command_result(result_message); 364 | } 365 | 366 | void ESP32BLEController::send_command_result(const char* format, ...) { 367 | char buffer[128]; 368 | va_list arg; 369 | va_start(arg, format); 370 | vsnprintf(buffer, sizeof(buffer), format, arg); 371 | va_end(arg); 372 | 373 | maintenance_handler->send_command_result(buffer); 374 | } 375 | 376 | #ifdef USE_BINARY_SENSOR 377 | void ESP32BLEController::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { update_component_state(obj, state); } 378 | #endif 379 | #ifdef USE_COVER 380 | void ESP32BLEController::on_cover_update(cover::Cover *obj) {} 381 | #endif 382 | #ifdef USE_FAN 383 | void ESP32BLEController::on_fan_update(fan::Fan *fan) { update_component_state(fan, fan->state); } 384 | #endif 385 | #ifdef USE_LIGHT 386 | void ESP32BLEController::on_light_update(light::LightState *obj) {} 387 | #endif 388 | #ifdef USE_SENSOR 389 | void ESP32BLEController::on_sensor_update(sensor::Sensor *component, float state) { update_component_state(component, state); } 390 | #endif 391 | #ifdef USE_SWITCH 392 | void ESP32BLEController::on_switch_update(switch_::Switch *obj, bool state) { update_component_state(obj, state); } 393 | #endif 394 | #ifdef USE_TEXT_SENSOR 395 | void ESP32BLEController::on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) { update_component_state(obj, state); } 396 | #endif 397 | #ifdef USE_CLIMATE 398 | void ESP32BLEController::on_climate_update(climate::Climate *obj) {} 399 | #endif 400 | 401 | template 402 | void ESP32BLEController::update_component_state(C* component, S state) { 403 | static_assert(std::is_base_of::value, "EntityBase subclasses expected"); 404 | 405 | auto object_id = component->get_object_id(); 406 | BLEComponentHandlerBase* handler = handler_for_component[object_id]; 407 | if (handler != nullptr) { 408 | handler->send_value(state); 409 | } 410 | } 411 | 412 | void ESP32BLEController::execute_in_loop(std::function&& deferred_function) { 413 | bool ok = deferred_functions_for_loop.push(std::move(deferred_function)); 414 | if (!ok) { 415 | ESP_LOGW(TAG, "Deferred functions queue full"); 416 | } 417 | } 418 | 419 | void ESP32BLEController::loop() { 420 | std::function deferred_function; 421 | while (deferred_functions_for_loop.take(deferred_function)) { 422 | deferred_function(); 423 | } 424 | } 425 | 426 | void ESP32BLEController::configure_ble_security() { 427 | if (get_security_mode() == BLESecurityMode::NONE) { 428 | return; 429 | } 430 | 431 | ESP_LOGD(TAG, " Setting up BLE security"); 432 | 433 | BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_MITM); 434 | BLEDevice::setSecurityCallbacks(this); 435 | 436 | // see https://github.com/espressif/esp-idf/blob/b0150615dff529662772a60dcb57d5b559f480e2/examples/bluetooth/bluedroid/ble/gatt_security_server/tutorial/Gatt_Security_Server_Example_Walkthrough.md 437 | BLESecurity security; 438 | security.setAuthenticationMode(get_security_mode() == BLESecurityMode::BOND ? ESP_LE_AUTH_BOND : ESP_LE_AUTH_REQ_SC_MITM_BOND); 439 | security.setCapability(can_show_pass_key ? ESP_IO_CAP_OUT : ESP_IO_CAP_NONE); 440 | security.setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); 441 | security.setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); 442 | security.setKeySize(16); 443 | 444 | uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_ENABLE; 445 | esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t)); 446 | } 447 | 448 | void ESP32BLEController::onPassKeyNotify(uint32_t pass_key) { 449 | char pass_key_digits[6 + 1]; 450 | snprintf(pass_key_digits, sizeof(pass_key_digits), "%06d", pass_key); 451 | string pass_key_str(pass_key_digits); 452 | 453 | auto& callbacks = on_show_pass_key_callbacks; 454 | global_ble_controller->execute_in_loop([&callbacks, pass_key_str](){ 455 | ESP_LOGI(TAG, "BLE authentication - pass received"); 456 | callbacks.call(pass_key_str); 457 | }); 458 | } 459 | 460 | void ESP32BLEController::onAuthenticationComplete(esp_ble_auth_cmpl_t result) { 461 | auto& callbacks = on_authentication_complete_callbacks; 462 | bool success=result.success; 463 | global_ble_controller->execute_in_loop([&callbacks, success](){ 464 | if (success) { 465 | ESP_LOGD(TAG, "BLE authentication - completed succesfully"); 466 | } else { 467 | ESP_LOGD(TAG, "BLE authentication - failed"); 468 | } 469 | callbacks.call(success); 470 | }); 471 | } 472 | 473 | uint32_t ESP32BLEController::onPassKeyRequest() { 474 | global_ble_controller->execute_in_loop([](){ ESP_LOGD(TAG, "onPassKeyRequest"); }); 475 | return 123456; 476 | } 477 | 478 | bool ESP32BLEController::onSecurityRequest() { 479 | global_ble_controller->execute_in_loop([](){ ESP_LOGD(TAG, "onSecurityRequest"); }); 480 | return true; 481 | } 482 | 483 | bool ESP32BLEController::onConfirmPIN(uint32_t pin) { 484 | global_ble_controller->execute_in_loop([](){ ESP_LOGD(TAG, "onConfirmPIN"); }); 485 | return true; 486 | } 487 | 488 | void ESP32BLEController::ESP32BLEController::onConnect(BLEServer* server) { 489 | auto& callbacks = on_connected_callbacks; 490 | global_ble_controller->execute_in_loop([&callbacks](){ 491 | ESP_LOGD(TAG, "BLE server - connected"); 492 | callbacks.call(); 493 | }); 494 | } 495 | 496 | void ESP32BLEController::ESP32BLEController::onDisconnect(BLEServer* server) { 497 | auto& callbacks = on_disconnected_callbacks; 498 | global_ble_controller->execute_in_loop([&callbacks, this](){ 499 | ESP_LOGD(TAG, "BLE server - disconnected"); 500 | 501 | // after 500ms start advertising again 502 | const uint32_t delay_millis = 500; 503 | App.scheduler.set_timeout(this, "", delay_millis, []{ BLEDevice::startAdvertising(); }); 504 | 505 | callbacks.call(); 506 | }); 507 | } 508 | 509 | ESP32BLEController* global_ble_controller = nullptr; 510 | 511 | } // namespace esp32_ble_controller 512 | } // namespace esphome 513 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/esp32_ble_controller.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "esphome/core/entity_base.h" 10 | #include "esphome/core/controller.h" 11 | #include "esphome/core/defines.h" 12 | #include "esphome/core/preferences.h" 13 | 14 | #include "ble_component_handler_base.h" 15 | #include "ble_maintenance_handler.h" 16 | #include "thread_safe_bounded_queue.h" 17 | #ifdef USE_WIFI 18 | #include "wifi_configuration_handler.h" 19 | #endif 20 | 21 | using std::string; 22 | using std::unordered_map; 23 | using std::vector; 24 | 25 | namespace esphome { 26 | namespace esp32_ble_controller { 27 | 28 | enum class BLEMaintenanceMode : uint8_t { MAINTENANCE_SERVICE = 1, COMPONENT_SERVICES = 2, NONE = 0, ALL = 3 }; 29 | 30 | enum class BLESecurityMode : uint8_t { NONE, SECURE, BOND }; 31 | 32 | class BLEControllerCustomCommandExecutionTrigger; 33 | 34 | /** 35 | * Bluetooth Low Energy controller for ESP32. 36 | * It provides a BLE server that can BLE clients like mobile phones can connect to and access components (like reading sensor values and control switches). 37 | * In addition it provides maintenance features like a BLE commands and logging over BLE. 38 | * 39 | * Besides the generic maintenance service, this controller only exposes components over BLE that have been registered before (i.e. configured explicitly in the yaml configuration). 40 | * @brief BLE controller for ESP32 41 | */ 42 | class ESP32BLEController : public Component, private BLESecurityCallbacks, private BLEServerCallbacks { 43 | public: 44 | ESP32BLEController(); 45 | virtual ~ESP32BLEController() {} 46 | 47 | // pre-setup configurations 48 | 49 | void register_component(EntityBase* component, const string& service_UUID, const string& characteristic_UUID, bool use_BLE2902 = true); 50 | 51 | void register_command(const string& name, const string& description, BLEControllerCustomCommandExecutionTrigger* trigger); 52 | const vector& get_commands() const; 53 | 54 | void add_on_show_pass_key_callback(std::function&& trigger_function); 55 | void add_on_authentication_complete_callback(std::function&& trigger_function); 56 | void add_on_connected_callback(std::function&& trigger_function); 57 | void add_on_disconnected_callback(std::function&& trigger_function); 58 | 59 | void set_maintenance_service_exposed_after_flash(bool exposed); 60 | 61 | void set_security_mode(BLESecurityMode mode) { security_mode = mode; } 62 | inline BLESecurityMode get_security_mode() const { return security_mode; } 63 | 64 | // deprecated 65 | void set_security_enabled(bool enabled); 66 | inline bool get_security_enabled() const { return security_mode != BLESecurityMode::NONE; } 67 | 68 | // setup 69 | 70 | float get_setup_priority() const override { return setup_priority::PROCESSOR; } 71 | 72 | void setup() override; 73 | 74 | void dump_config() override; 75 | 76 | // run 77 | 78 | void loop() override; 79 | 80 | inline BLEMaintenanceMode get_ble_mode() const { return ble_mode; } 81 | bool get_maintenance_service_exposed() const { return static_cast(ble_mode) & static_cast(BLEMaintenanceMode::MAINTENANCE_SERVICE); } 82 | bool get_component_services_exposed() const { return static_cast(ble_mode) & static_cast(BLEMaintenanceMode::COMPONENT_SERVICES); } 83 | void switch_ble_mode(BLEMaintenanceMode mode); 84 | void switch_maintenance_service_exposed(bool exposed); 85 | void switch_component_services_exposed(bool exposed); 86 | 87 | #ifdef USE_LOGGER 88 | int get_log_level() { return maintenance_handler->get_log_level(); } 89 | void set_log_level(int level) { maintenance_handler->set_log_level(level); } 90 | #endif 91 | 92 | #ifdef USE_WIFI 93 | void set_wifi_configuration(const string& ssid, const string& password, bool hidden_network); 94 | void clear_wifi_configuration_and_reboot(); 95 | const optional get_current_ssid_in_wifi_configuration(); 96 | #endif 97 | 98 | void send_command_result(const string& result_message); 99 | void send_command_result(const char* result_msg_format, ...); 100 | 101 | /// Executes a given function in the main loop of the app. (Can be called from another RTOS task.) 102 | void execute_in_loop(std::function&& deferred_function); 103 | 104 | private: 105 | void initialize_ble_mode(); 106 | 107 | bool setup_ble(); 108 | void setup_ble_server_and_services(); 109 | void setup_ble_services_for_components(); 110 | template void setup_ble_services_for_components(const vector& components, BLEComponentHandlerBase* (*handler_creator)(C*, const BLECharacteristicInfoForHandler&)); 111 | template void setup_ble_service_for_component(C* component, BLEComponentHandlerBase* (*handler_creator)(C*, const BLECharacteristicInfoForHandler&)); 112 | template void update_component_state(C* component, S state); 113 | 114 | // Controller methods: 115 | void register_state_change_callbacks_and_send_initial_states(); 116 | #ifdef USE_BINARY_SENSOR 117 | void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state); 118 | #endif 119 | #ifdef USE_FAN 120 | void on_fan_update(fan::Fan *obj); 121 | #endif 122 | #ifdef USE_LIGHT 123 | void on_light_update(light::LightState *obj); 124 | #endif 125 | #ifdef USE_SENSOR 126 | void on_sensor_update(sensor::Sensor *obj, float state); 127 | #endif 128 | #ifdef USE_SWITCH 129 | void on_switch_update(switch_::Switch *obj, bool state); 130 | #endif 131 | #ifdef USE_COVER 132 | void on_cover_update(cover::Cover *obj); 133 | #endif 134 | #ifdef USE_TEXT_SENSOR 135 | void on_text_sensor_update(text_sensor::TextSensor *obj, std::string state); 136 | #endif 137 | #ifdef USE_CLIMATE 138 | void on_climate_update(climate::Climate *obj); 139 | #endif 140 | 141 | void configure_ble_security(); 142 | virtual uint32_t onPassKeyRequest(); // inherited from BLESecurityCallbacks 143 | virtual void onPassKeyNotify(uint32_t pass_key); // inherited from BLESecurityCallbacks 144 | virtual bool onSecurityRequest(); // inherited from BLESecurityCallbacks 145 | virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t); // inherited from BLESecurityCallbacks 146 | virtual bool onConfirmPIN(uint32_t pin); // inherited from BLESecurityCallbacks 147 | 148 | virtual void onConnect(BLEServer* server); // inherited from BLEServerCallbacks 149 | virtual void onDisconnect(BLEServer* server); // inherited from BLEServerCallbacks 150 | 151 | private: 152 | BLEServer* ble_server; 153 | 154 | BLEMaintenanceMode initial_ble_mode_after_flashing{BLEMaintenanceMode::ALL}; 155 | BLEMaintenanceMode ble_mode; 156 | ESPPreferenceObject ble_mode_preference; 157 | 158 | BLESecurityMode security_mode{BLESecurityMode::SECURE}; 159 | bool can_show_pass_key{false}; 160 | 161 | BLEMaintenanceHandler* maintenance_handler; 162 | 163 | #ifdef USE_WIFI 164 | WifiConfigurationHandler wifi_configuration_handler; 165 | #endif 166 | 167 | unordered_map info_for_component; 168 | unordered_map handler_for_component; 169 | 170 | ThreadSafeBoundedQueue> deferred_functions_for_loop{16}; 171 | 172 | CallbackManager on_show_pass_key_callbacks; 173 | CallbackManager on_authentication_complete_callbacks; 174 | CallbackManager on_connected_callbacks; 175 | CallbackManager on_disconnected_callbacks; 176 | }; 177 | 178 | /// The BLE controller singleton. 179 | extern ESP32BLEController* global_ble_controller; 180 | 181 | } // namespace esp32_ble_controller 182 | } // namespace esphome 183 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/thread_safe_bounded_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace esphome { 7 | namespace esp32_ble_controller { 8 | 9 | /** 10 | * Thread-safe non-blocking bounded queue to pass values between Free RTOS tasks. 11 | */ 12 | template 13 | class ThreadSafeBoundedQueue { 14 | public: 15 | /// Creates a bounded queue with the given size (i.e. maximum number of objects that can be queued). 16 | ThreadSafeBoundedQueue(unsigned int size); 17 | 18 | /** 19 | * Pushes the given object into the queue, the queue takes over ownership. 20 | * @param object object to append to the queue (treated as r-value) 21 | * @return true if successful, false if queue is full 22 | */ 23 | bool push(T&& object); 24 | 25 | /** 26 | * Takes the first queued element from the queue (if any) and moves it to the given object. 27 | * @param object object to store the dequeued value 28 | * @return true if successful, false if queue was empty 29 | */ 30 | bool take(T& object); 31 | 32 | private: 33 | QueueHandle_t queue; 34 | }; 35 | 36 | template 37 | ThreadSafeBoundedQueue::ThreadSafeBoundedQueue(unsigned int size) { 38 | queue = xQueueCreate( size, sizeof( void* ) ); 39 | } 40 | 41 | template 42 | bool ThreadSafeBoundedQueue::push(T&& object) { 43 | T* pointer_to_copy = new T(); 44 | *pointer_to_copy = std::move(object); 45 | 46 | // add the pointer to the queue, not the object itself 47 | auto result = xQueueSend(queue, &pointer_to_copy, 20L / portTICK_PERIOD_MS); 48 | return result == pdPASS; 49 | } 50 | 51 | template 52 | bool ThreadSafeBoundedQueue::take(T& object) { 53 | T* pointer_to_object; 54 | auto result = xQueueReceive(queue, &pointer_to_object, 0); 55 | if (result != pdPASS) { 56 | return false; 57 | } 58 | 59 | object = std::move(*pointer_to_object); 60 | delete pointer_to_object; 61 | return true; 62 | } 63 | 64 | } // namespace esp32_ble_controller 65 | } // namespace esphome 66 | -------------------------------------------------------------------------------- /components/esp32_ble_controller/wifi_configuration_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "wifi_configuration_handler.h" 2 | 3 | #ifdef USE_WIFI 4 | 5 | #include "esphome/core/application.h" 6 | #include "esphome/core/log.h" 7 | #include "esphome/components/wifi/wifi_component.h" 8 | 9 | namespace esphome { 10 | namespace esp32_ble_controller { 11 | 12 | static const char *TAG = "wifi_configuration_handler"; 13 | 14 | void WifiConfigurationHandler::setup() { 15 | // Hash with compilation time 16 | // This ensures the AP override is not applied for OTA 17 | uint32_t hash = fnv1_hash("wifi_configuration#" + App.get_compilation_time()); 18 | wifi_configuration_preference = global_preferences->make_preference(hash, true); 19 | 20 | WifiConfiguration configuration; 21 | if (load_configuration(configuration)) { 22 | ESP_LOGI(TAG, "Overriding WIFI configuration with stored preferences"); 23 | override_sta(configuration); 24 | } 25 | } 26 | 27 | void WifiConfigurationHandler::set_credentials(const std::string &ssid, const std::string &password, bool hidden_network) { 28 | ESP_LOGI(TAG, "Updating WIFI configuration"); 29 | 30 | WifiConfiguration configuration; 31 | 32 | strncpy(configuration.ssid, ssid.c_str(), WIFI_SSID_LEN); 33 | strncpy(configuration.password, password.c_str(), WIFI_PASSWORD_LEN); 34 | configuration.hidden_network = hidden_network; 35 | 36 | if (!save_configuration(configuration)) { 37 | ESP_LOGE(TAG, "Could not save new WIFI configuration"); 38 | } 39 | 40 | override_sta(configuration); 41 | } 42 | 43 | void WifiConfigurationHandler::clear_credentials() { 44 | ESP_LOGI(TAG, "Clearing WIFI configuration"); 45 | 46 | WifiConfiguration configuration; 47 | configuration.ssid[0] = 0; 48 | 49 | if (!save_configuration(configuration)) { 50 | ESP_LOGE(TAG, "Could not clear WIFI configuration"); 51 | } 52 | } 53 | 54 | const optional WifiConfigurationHandler::get_current_ssid() const { 55 | WifiConfiguration configuration; 56 | if (load_configuration(configuration)) { 57 | return make_optional(configuration.ssid); 58 | } else { 59 | return optional(); 60 | } 61 | } 62 | 63 | bool WifiConfigurationHandler::load_configuration(WifiConfiguration& configuration) const { 64 | return const_cast(this)->wifi_configuration_preference.load(&configuration) && strlen(configuration.ssid); 65 | } 66 | 67 | bool WifiConfigurationHandler::save_configuration(const WifiConfiguration& configuration) { 68 | return wifi_configuration_preference.save(&configuration); 69 | } 70 | 71 | void WifiConfigurationHandler::override_sta(const WifiConfiguration& configuration) { 72 | wifi::WiFiAP sta; 73 | 74 | sta.set_ssid(configuration.ssid); 75 | sta.set_password(configuration.password); 76 | sta.set_hidden(configuration.hidden_network); 77 | 78 | wifi::global_wifi_component->set_sta(sta); 79 | } 80 | 81 | } // namespace esp32_ble_controller 82 | } // namespace esphome 83 | 84 | #endif -------------------------------------------------------------------------------- /components/esp32_ble_controller/wifi_configuration_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "esphome/core/defines.h" 6 | #include "esphome/core/preferences.h" 7 | #include "esphome/core/optional.h" 8 | 9 | #ifdef USE_WIFI 10 | 11 | namespace esphome { 12 | namespace esp32_ble_controller { 13 | 14 | static const uint8_t WIFI_SSID_LEN = 33; 15 | static const uint8_t WIFI_PASSWORD_LEN = 65; 16 | 17 | struct WifiConfiguration { 18 | char ssid[WIFI_SSID_LEN]; 19 | char password[WIFI_PASSWORD_LEN]; 20 | bool hidden_network; 21 | } PACKED; // NOLINT 22 | 23 | class WifiConfigurationHandler { 24 | public: 25 | void setup(); 26 | 27 | void set_credentials(const std::string& ssid, const std::string& password, bool hidden_network); 28 | void clear_credentials(); 29 | 30 | const optional get_current_ssid() const; 31 | 32 | private: 33 | bool load_configuration(WifiConfiguration& configuration) const; 34 | bool save_configuration(const WifiConfiguration& configuration); 35 | void override_sta(const WifiConfiguration& configuration); 36 | 37 | private: 38 | ESPPreferenceObject wifi_configuration_preference; 39 | }; 40 | 41 | } // namespace esp32_ble_controller 42 | } // namespace esphome 43 | 44 | 45 | #endif --------------------------------------------------------------------------------