diff --git a/README.md b/README.md index eba031a..bfc4900 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,11 @@ esp32_ble_controller: # This automation is not available for the "none" mode, optional for the "bond" mode, and required for the "secure" mode. security_mode: secure + # allows to disable the maintenance service, default is 'true' + # When 'false', the maintenance service is not exposed, which provides at least some protection when security mode is "none". + # Note: Writeable characteristics like those for switches or fans may still be written by basically anyone. + maintenance: true + # automation that is invoked when the pass key should be displayed, the pass key is available in the automation as "pass_key" variable of type std::string (not available if security mode is "none") # the example below just logs the pass keys on_show_pass_key: @@ -104,7 +109,7 @@ Note: On some computers (like the MacBook Pro for example) the very first bondin ### Maintenance service -The maintenance BLE service is provided implicitly when you include `esp32_ble_controller` in your yaml configuration. It provides two characteristics: +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: * Command channel (UTF-8 string, read-write): 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. @@ -112,8 +117,10 @@ You can define your own custom commands in yaml as described below in detail. There are also some built-in commands, which are always available: * help [<command>]: 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. + * ble-maintenance [off]: + 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). * ble-services [on|off]: - Switches the component related (non-maintenance) BLE services on or off. 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.) + 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.) * wifi-config <ssid> <password> [hidden]: 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.) * parings [clear]: diff --git a/components/esp32_ble_controller/__init__.py b/components/esp32_ble_controller/__init__.py index 457cd4e..81517aa 100644 --- a/components/esp32_ble_controller/__init__.py +++ b/components/esp32_ble_controller/__init__.py @@ -17,7 +17,7 @@ ### Configuration validation ############################################################################################ -# BLE services and characteristics ##### +# BLE component services and characteristics ##### CONF_BLE_SERVICES = "services" CONF_BLE_SERVICE = "service" CONF_BLE_CHARACTERISTICS = "characteristics" @@ -72,6 +72,9 @@ def validate_command_id(value): }), }) +# BLE maintenance services ##### +CONF_EXPOSE_MAINTENANCE_SERVICE = "maintenance" + # security mode enumeration ##### CONF_SECURITY_MODE = 'security_mode' BLESecurityMode = esp32_ble_controller_ns.enum("BLESecurityMode", is_class = True) @@ -126,6 +129,8 @@ def required_automations_present(config): cv.Optional(CONF_BLE_COMMANDS): cv.ensure_list(BLE_COMMAND), + cv.Optional(CONF_EXPOSE_MAINTENANCE_SERVICE, default=True): cv.boolean, + cv.Optional(CONF_SECURITY_MODE, default=CONF_SECURITY_MODE_SECURE): cv.enum(SECURTY_MODE_OPTIONS), cv.Optional(CONF_ON_SHOW_PASS_KEY): automation.validate_automation({ @@ -185,6 +190,8 @@ def to_code(config): for cmd in config.get(CONF_BLE_COMMANDS, []): yield to_code_command(var, cmd) + cg.add(var.set_maintenance_service_exposed_after_flash(config[CONF_EXPOSE_MAINTENANCE_SERVICE])) + security_enabled = SECURTY_MODE_OPTIONS[config[CONF_SECURITY_MODE]] cg.add(var.set_security_mode(config[CONF_SECURITY_MODE])) diff --git a/components/esp32_ble_controller/ble_command.cpp b/components/esp32_ble_controller/ble_command.cpp index 9203a34..1e9bcb0 100644 --- a/components/esp32_ble_controller/ble_command.cpp +++ b/components/esp32_ble_controller/ble_command.cpp @@ -46,21 +46,29 @@ void BLECommandHelp::execute(const vector& arguments) const { } } +// ble-maintenance /////////////////////////////////////////////////////////////////////////////////////////////// + +BLECommandSwitchMaintenanceOnOrOff::BLECommandSwitchMaintenanceOnOrOff() : BLECommand("ble-maintenance", "'ble-maintenance off' disables the maintenance BLE service.") {} + +void BLECommandSwitchMaintenanceOnOrOff::execute(const vector& arguments) const { + if (!arguments.empty()) { + const string& on_or_off = arguments[0]; + global_ble_controller->switch_maintenance_service_exposed(on_or_off != "off"); + } + string enabled_or_disabled = global_ble_controller->get_component_services_exposed() ? "disabled" : "enabled"; + set_result("Maintenance services is " + enabled_or_disabled +"."); +} + // ble-services /////////////////////////////////////////////////////////////////////////////////////////////// -BLECommandSwitchServicesOnOrOff::BLECommandSwitchServicesOnOrOff() : BLECommand("ble-services", "'ble-services on|off' enables or disables the non-maintenance BLE services.") {} +BLECommandSwitchComponentServicesOnOrOff::BLECommandSwitchComponentServicesOnOrOff() : BLECommand("ble-services", "'ble-services on|off' enables or disables the non-maintenance BLE services.") {} -void BLECommandSwitchServicesOnOrOff::execute(const vector& arguments) const { +void BLECommandSwitchComponentServicesOnOrOff::execute(const vector& arguments) const { if (!arguments.empty()) { const string& on_or_off = arguments[0]; - if (on_or_off == "off") { - global_ble_controller->set_ble_mode(BLEMaintenanceMode::WIFI_ONLY); - } else { - global_ble_controller->set_ble_mode(BLEMaintenanceMode::MIXED); - } + global_ble_controller->switch_component_services_exposed(on_or_off != "off"); } - BLEMaintenanceMode mode = global_ble_controller->get_ble_mode(); - string enabled_or_disabled = mode == BLEMaintenanceMode::WIFI_ONLY ? "disabled" : "enabled"; + string enabled_or_disabled = global_ble_controller->get_component_services_exposed() ? "disabled" : "enabled"; set_result("Non-maintenance services are " + enabled_or_disabled +"."); } diff --git a/components/esp32_ble_controller/ble_command.h b/components/esp32_ble_controller/ble_command.h index e85a596..439b05f 100644 --- a/components/esp32_ble_controller/ble_command.h +++ b/components/esp32_ble_controller/ble_command.h @@ -43,12 +43,22 @@ class BLECommandHelp : public BLECommand { virtual void execute(const vector& arguments) const override; }; +// ble-maintenance /////////////////////////////////////////////////////////////////////////////////////////////// + +class BLECommandSwitchMaintenanceOnOrOff : public BLECommand { +public: + BLECommandSwitchMaintenanceOnOrOff(); + virtual ~BLECommandSwitchMaintenanceOnOrOff() {} + + virtual void execute(const vector& arguments) const override; +}; + // ble-services /////////////////////////////////////////////////////////////////////////////////////////////// -class BLECommandSwitchServicesOnOrOff : public BLECommand { +class BLECommandSwitchComponentServicesOnOrOff : public BLECommand { public: - BLECommandSwitchServicesOnOrOff(); - virtual ~BLECommandSwitchServicesOnOrOff() {} + BLECommandSwitchComponentServicesOnOrOff(); + virtual ~BLECommandSwitchComponentServicesOnOrOff() {} virtual void execute(const vector& arguments) const override; }; diff --git a/components/esp32_ble_controller/ble_maintenance_handler.cpp b/components/esp32_ble_controller/ble_maintenance_handler.cpp index 07c72cd..5eba912 100644 --- a/components/esp32_ble_controller/ble_maintenance_handler.cpp +++ b/components/esp32_ble_controller/ble_maintenance_handler.cpp @@ -23,9 +23,10 @@ namespace esp32_ble_controller { static const char *TAG = "ble_maintenance_handler"; -BLEMaintenanceHandler::BLEMaintenanceHandler() { +BLEMaintenanceHandler::BLEMaintenanceHandler() : ble_command_characteristic(nullptr) { commands.push_back(new BLECommandHelp()); - commands.push_back(new BLECommandSwitchServicesOnOrOff()); + commands.push_back(new BLECommandSwitchMaintenanceOnOrOff()); + commands.push_back(new BLECommandSwitchComponentServicesOnOrOff()); #ifdef USE_WIFI commands.push_back(new BLECommandWifiConfiguration()); #endif @@ -33,6 +34,9 @@ BLEMaintenanceHandler::BLEMaintenanceHandler() { commands.push_back(new BLECommandVersion()); #ifdef USE_LOGGER + log_level = ESPHOME_LOG_LEVEL; + logging_characteristic = nullptr; + commands.push_back(new BLECommandLogLevel()); #endif } @@ -52,8 +56,7 @@ void BLEMaintenanceHandler::setup(BLEServer* ble_server) { service->start(); #ifdef USE_LOGGER - log_level = ESPHOME_LOG_LEVEL; - if (global_ble_controller->get_ble_mode() != BLEMaintenanceMode::BLE_ONLY) { + if (!global_ble_controller->get_component_services_exposed()) { log_level = ESPHOME_LOG_LEVEL_CONFIG; } @@ -94,9 +97,12 @@ void BLEMaintenanceHandler::on_command_written() { } void BLEMaintenanceHandler::send_command_result(const string& result_message) { - global_ble_controller->execute_in_loop([this, result_message] { - ble_command_characteristic->setValue(result_message); - }); + if (ble_command_characteristic != nullptr) { + global_ble_controller->execute_in_loop([this, result_message] { + ble_command_characteristic->setValue(result_message); + }); + } + // global_ble_controller->execute_in_loop([this, result_message] { // const uint32_t delay_millis = 50; // App.scheduler.set_timeout(global_ble_controller, "command_result", delay_millis, [this, result_message]{ ble_command_characteristic->setValue(result_message); }); @@ -129,7 +135,7 @@ string remove_logger_magic(const string& message) { } void BLEMaintenanceHandler::send_log_message(int level, const char *tag, const char *message) { - if (level <= this->log_level) { + if (logging_characteristic != nullptr && level <= this->log_level) { logging_characteristic->setValue(remove_logger_magic(message)); logging_characteristic->notify(); } diff --git a/components/esp32_ble_controller/esp32_ble_controller.cpp b/components/esp32_ble_controller/esp32_ble_controller.cpp index 962db9d..891165c 100644 --- a/components/esp32_ble_controller/esp32_ble_controller.cpp +++ b/components/esp32_ble_controller/esp32_ble_controller.cpp @@ -59,6 +59,18 @@ void ESP32BLEController::add_on_disconnected_callback(std::function&& tr on_disconnected_callbacks.add(std::move(trigger_function)); } +BLEMaintenanceMode set_feature(BLEMaintenanceMode current_mode, BLEMaintenanceMode feature, bool is_set) { + uint8_t new_mode = static_cast(current_mode) & (~static_cast(feature)); + if (is_set) { + new_mode |= static_cast(feature); + } + return static_cast(new_mode); +} + +void ESP32BLEController::set_maintenance_service_exposed_after_flash(bool exposed) { + initial_ble_mode_after_flashing = set_feature(initial_ble_mode_after_flashing, BLEMaintenanceMode::MAINTENANCE_SERVICE, exposed); +} + void ESP32BLEController::set_security_enabled(bool enabled) { set_security_mode(BLESecurityMode::SECURE); } @@ -138,9 +150,11 @@ void ESP32BLEController::setup_ble_server_and_services() { ble_server = BLEDevice::createServer(); ble_server->setCallbacks(this); - maintenance_handler->setup(ble_server); + if (get_maintenance_service_exposed()) { + maintenance_handler->setup(ble_server); + } - if (get_ble_mode() != BLEMaintenanceMode::WIFI_ONLY) { + if (get_component_services_exposed()) { setup_ble_services_for_components(); } } @@ -265,36 +279,32 @@ void ESP32BLEController::initialize_ble_mode() { // Note: We include the compilation time to force a reset after flashing new firmware ble_mode_preference = global_preferences->make_preference(fnv1_hash("ble-mode#" + App.get_compilation_time())); - uint8_t mode; - if (!ble_mode_preference.load(&mode)) { - mode = (uint8_t) BLEMaintenanceMode::BLE_ONLY; + if (!ble_mode_preference.load(&ble_mode)) { + ble_mode = initial_ble_mode_after_flashing; } - ble_mode = static_cast(mode); - - ESP_LOGCONFIG(TAG, "BLE mode: %d", mode); + ESP_LOGCONFIG(TAG, "BLE mode: %d", static_cast(ble_mode)); } -void ESP32BLEController::set_ble_mode(BLEMaintenanceMode mode) { - set_ble_mode((uint8_t) mode); -} +void ESP32BLEController::switch_ble_mode(BLEMaintenanceMode newMode) { + if (ble_mode != newMode) { + ESP_LOGI(TAG, "Switching BLE mode to %d and rebooting", static_cast(newMode)); -void ESP32BLEController::set_ble_mode(uint8_t newMode) { - if (newMode > (uint8_t) BLEMaintenanceMode::WIFI_ONLY) { - ESP_LOGI(TAG, "Ignoring unsupported BLE mode %d", newMode); - return; - } - - ESP_LOGI(TAG, "Updating BLE mode to %d", newMode); - BLEMaintenanceMode newBleMode = static_cast(newMode); - if (ble_mode != newBleMode) { - ble_mode = newBleMode; + ble_mode = newMode; ble_mode_preference.save(&ble_mode); App.safe_reboot(); } } +void ESP32BLEController::switch_maintenance_service_exposed(bool exposed) { + switch_ble_mode(set_feature(ble_mode, BLEMaintenanceMode::MAINTENANCE_SERVICE, exposed)); +} + +void ESP32BLEController::switch_component_services_exposed(bool exposed) { + switch_ble_mode(set_feature(ble_mode, BLEMaintenanceMode::COMPONENT_SERVICES, exposed)); +} + void ESP32BLEController::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Low Energy Controller:"); ESP_LOGCONFIG(TAG, " BLE device address: %s", BLEDevice::getAddress().toString().c_str()); diff --git a/components/esp32_ble_controller/esp32_ble_controller.h b/components/esp32_ble_controller/esp32_ble_controller.h index b4518c5..5ed694c 100644 --- a/components/esp32_ble_controller/esp32_ble_controller.h +++ b/components/esp32_ble_controller/esp32_ble_controller.h @@ -25,7 +25,7 @@ using std::vector; namespace esphome { namespace esp32_ble_controller { -enum class BLEMaintenanceMode : uint8_t { BLE_ONLY, MIXED, WIFI_ONLY }; +enum class BLEMaintenanceMode : uint8_t { MAINTENANCE_SERVICE = 1, COMPONENT_SERVICES = 2, ALL = 3 }; enum class BLESecurityMode : uint8_t { NONE, SECURE, BOND }; @@ -56,6 +56,8 @@ class ESP32BLEController : public Component, private BLESecurityCallbacks, priva void add_on_connected_callback(std::function&& trigger_function); void add_on_disconnected_callback(std::function&& trigger_function); + void set_maintenance_service_exposed_after_flash(bool exposed); + void set_security_mode(BLESecurityMode mode) { security_mode = mode; } inline BLESecurityMode get_security_mode() const { return security_mode; } @@ -76,8 +78,11 @@ class ESP32BLEController : public Component, private BLESecurityCallbacks, priva void loop() override; inline BLEMaintenanceMode get_ble_mode() const { return ble_mode; } - void set_ble_mode(BLEMaintenanceMode mode); - void set_ble_mode(uint8_t mode); + bool get_maintenance_service_exposed() const { return static_cast(ble_mode) & static_cast(BLEMaintenanceMode::MAINTENANCE_SERVICE); } + bool get_component_services_exposed() const { return static_cast(ble_mode) & static_cast(BLEMaintenanceMode::COMPONENT_SERVICES); } + void switch_ble_mode(BLEMaintenanceMode mode); + void switch_maintenance_service_exposed(bool exposed); + void switch_component_services_exposed(bool exposed); #ifdef USE_LOGGER int get_log_level() { return maintenance_handler->get_log_level(); } @@ -146,6 +151,7 @@ class ESP32BLEController : public Component, private BLESecurityCallbacks, priva private: BLEServer* ble_server; + BLEMaintenanceMode initial_ble_mode_after_flashing{BLEMaintenanceMode::ALL}; BLEMaintenanceMode ble_mode; ESPPreferenceObject ble_mode_preference;