diff --git a/esphome/components/esp32_ble_controller/__init__.py b/esphome/components/esp32_ble_controller/__init__.py new file mode 100644 index 0000000000..6a7fd367c1 --- /dev/null +++ b/esphome/components/esp32_ble_controller/__init__.py @@ -0,0 +1,60 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import esp32_ble_server, logger +from esphome.const import ( + CONF_BLE_SERVER_ID, + CONF_ID, + CONF_LEVEL, + CONF_LOGGER, + ESP_PLATFORM_ESP32, +) + +AUTO_LOAD = ["esp32_ble_server"] +ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +CODEOWNERS = ["@jesserockz"] +CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] + +CONF_LOG_LEVEL = "log_level" + +esp32_ble_controller_ns = cg.esphome_ns.namespace("esp32_ble_controller") +BLEController = esp32_ble_controller_ns.class_( + "BLEController", + cg.Component, + cg.Controller, + esp32_ble_server.BLEServiceComponent, +) + + +def validate(config, item_config): + global_level = config[CONF_LOGGER][CONF_LEVEL] + level = item_config.get(CONF_LOG_LEVEL, "DEBUG") + if logger.LOG_LEVEL_SEVERITY.index(level) > logger.LOG_LEVEL_SEVERITY.index( + global_level + ): + raise ValueError( + "The esp32_ble_controller log level {} must be less severe than the " + "global log level {}.".format(level, global_level) + ) + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(BLEController), + cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble_server.BLEServer), + cv.Optional(CONF_LOG_LEVEL): cv.All( + cv.requires_component("logger"), + logger.is_log_level, + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + ble_server = await cg.get_variable(config[CONF_BLE_SERVER_ID]) + cg.add(ble_server.register_service_component(var)) + + if CONF_LOG_LEVEL in config: + cg.add(var.set_log_level(logger.LOG_LEVELS[config[CONF_LOG_LEVEL]])) diff --git a/esphome/components/esp32_ble_controller/ble_controller.cpp b/esphome/components/esp32_ble_controller/ble_controller.cpp new file mode 100644 index 0000000000..eadf9bbdde --- /dev/null +++ b/esphome/components/esp32_ble_controller/ble_controller.cpp @@ -0,0 +1,387 @@ +#include "ble_controller.h" + +#include "esphome/core/application.h" +#include "esphome/components/esp32_ble_server/ble_2901.h" +#include "esphome/components/esp32_ble_server/ble_2902.h" + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace esp32_ble_controller { + +static const char *const ESPHOME_SERVICE_UUID = "03774663-d394-496e-8dcd-000000000000"; +static const char *const LOGGER_CHARACTERISTIC_UUID = "03774663-d394-496e-8dcd-000000000001"; + +static const char *const BINARY_SENSOR_SERVICE_UUID = "03774663-d394-496e-8dcd-000100000000"; +static const char *const COVER_SERVICE_UUID = "03774663-d394-496e-8dcd-000200000000"; +static const char *const FAN_SERVICE_UUID = "03774663-d394-496e-8dcd-000300000000"; +static const char *const LIGHT_SERVICE_UUID = "03774663-d394-496e-8dcd-000400000000"; +static const char *const SENSOR_SERVICE_UUID = "03774663-d394-496e-8dcd-000500000000"; +static const char *const SWITCH_SERVICE_UUID = "03774663-d394-496e-8dcd-000600000000"; +static const char *const TEXT_SENSOR_SERVICE_UUID = "03774663-d394-496e-8dcd-000700000000"; +static const char *const CLIMATE_SERVICE_UUID = "03774663-d394-496e-8dcd-000800000000"; + +static const char *const TAG = "esp32_ble_controller"; + +void BLEController::setup() { + ESP_LOGD(TAG, "Setting up BLE controller"); + this->esphome_service_ = global_ble_server->create_service(ESPHOME_SERVICE_UUID); + +#ifdef USE_LOGGER + { + this->logger_characteristic_ = this->esphome_service_->create_characteristic( + LOGGER_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + + BLEDescriptor *logger_name = new BLE2901("Logger"); + this->logger_characteristic_->add_descriptor(logger_name); + + BLEDescriptor *descriptor_2902 = new BLE2902(); + this->logger_characteristic_->add_descriptor(descriptor_2902); + } +#endif + +#ifdef USE_BINARY_SENSOR + { + auto binary_sensors = App.get_binary_sensors(); + if (!binary_sensors.empty()) { + this->binary_sensor_service_ = global_ble_server->create_service(BINARY_SENSOR_SERVICE_UUID); + for (auto *obj : binary_sensors) { + std::string uuid = std::string(BINARY_SENSOR_SERVICE_UUID).substr(0, 28); + uuid += uint32_to_string(obj->get_object_id_hash()); + BLECharacteristic *characteristic = this->binary_sensor_service_->create_characteristic( + uuid, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + + BLEDescriptor *name = new BLE2901(obj->get_name()); + characteristic->add_descriptor(name); + + BLEDescriptor *descriptor = new BLE2902(); + characteristic->add_descriptor(descriptor); + + this->characteristics_.insert( + std::pair(obj->get_object_id_hash(), characteristic)); + } + } + } +#endif +#ifdef USE_COVER + if (!App.get_covers().empty()) { + this->cover_service_ = global_ble_server->create_service(COVER_SERVICE_UUID); + } +#endif +#ifdef USE_FAN + if (!App.get_fans().empty()) { + this->fan_service_ = global_ble_server->create_service(FAN_SERVICE_UUID); + } +#endif +#ifdef USE_LIGHT + if (!App.get_lights().empty()) { + this->light_service_ = global_ble_server->create_service(LIGHT_SERVICE_UUID); + } +#endif +#ifdef USE_SENSOR + { + auto sensors = App.get_sensors(); + if (!sensors.empty()) { + this->sensor_service_ = global_ble_server->create_service(SENSOR_SERVICE_UUID); + for (auto *obj : sensors) { + std::string uuid = std::string(SENSOR_SERVICE_UUID).substr(0, 28); + uuid += uint32_to_string(obj->get_object_id_hash()); + BLECharacteristic *characteristic = this->sensor_service_->create_characteristic( + uuid, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + + BLEDescriptor *name = new BLE2901(obj->get_name()); + characteristic->add_descriptor(name); + + BLEDescriptor *descriptor = new BLE2902(); + characteristic->add_descriptor(descriptor); + + this->characteristics_.insert( + std::pair(obj->get_object_id_hash(), characteristic)); + } + } + } +#endif +#ifdef USE_SWITCH + { + auto switches = App.get_switches(); + if (!switches.empty()) { + this->switch_service_ = global_ble_server->create_service(SWITCH_SERVICE_UUID); + for (auto *obj : switches) { + std::string uuid = std::string(SWITCH_SERVICE_UUID).substr(0, 28); + uuid += uint32_to_string(obj->get_object_id_hash()); + BLECharacteristic *characteristic = this->switch_service_->create_characteristic( + uuid, + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE); + + BLEDescriptor *name = new BLE2901(obj->get_name()); + characteristic->add_descriptor(name); + + BLEDescriptor *descriptor = new BLE2902(); + characteristic->add_descriptor(descriptor); + + this->characteristics_.insert( + std::pair(obj->get_object_id_hash(), characteristic)); + + characteristic->on_write([obj](std::vector data) { + if (data[0]) + obj->turn_on(); + else + obj->turn_off(); + }); + } + } + } +#endif +#ifdef USE_TEXT_SENSOR + { + auto text_sensors = App.get_text_sensors(); + if (!text_sensors.empty()) { + this->text_sensor_service_ = global_ble_server->create_service(TEXT_SENSOR_SERVICE_UUID); + for (auto *obj : text_sensors) { + std::string uuid = std::string(TEXT_SENSOR_SERVICE_UUID).substr(0, 28); + uuid += uint32_to_string(obj->get_object_id_hash()); + BLECharacteristic *characteristic = this->text_sensor_service_->create_characteristic( + uuid, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + + BLEDescriptor *name = new BLE2901(obj->get_name()); + characteristic->add_descriptor(name); + + BLEDescriptor *descriptor = new BLE2902(); + characteristic->add_descriptor(descriptor); + + this->characteristics_.insert( + std::pair(obj->get_object_id_hash(), characteristic)); + } + } + } +#endif +#ifdef USE_CLIMATE + if (!App.get_climates().empty()) { + this->cover_service_ = global_ble_server->create_service(COVER_SERVICE_UUID); + } +#endif + this->state_ = CREATING; + this->setup_controller(); +} + +void BLEController::loop() { + switch (this->state_) { + case CREATING: { + bool all_created = true; + all_created &= this->esphome_service_->is_created(); +#ifdef USE_BINARY_SENSOR + all_created &= this->binary_sensor_service_ == nullptr || this->binary_sensor_service_->is_created(); +#endif +#ifdef USE_COVER + all_created &= this->cover_service_ == nullptr || this->cover_service_->is_created(); +#endif +#ifdef USE_FAN + all_created &= this->fan_service_ == nullptr || this->fan_service_->is_created(); +#endif +#ifdef USE_LIGHT + all_created &= this->light_service_ == nullptr || this->light_service_->is_created(); +#endif +#ifdef USE_SENSOR + all_created &= this->sensor_service_ == nullptr || this->sensor_service_->is_created(); +#endif +#ifdef USE_SWITCH + all_created &= this->switch_service_ == nullptr || this->switch_service_->is_created(); +#endif +#ifdef USE_TEXT_SENSOR + all_created &= this->text_sensor_service_ == nullptr || this->text_sensor_service_->is_created(); +#endif +#ifdef USE_CLIMATE + all_created &= this->climate_service_ == nullptr || this->climate_service_->is_created(); +#endif + if (all_created) { + ESP_LOGI(TAG, "All services created"); + this->state_ = STARTING; + } + break; + } + case STARTING: { + bool all_running = true; + + all_running &= this->esphome_service_->is_running(); + +#ifdef USE_BINARY_SENSOR + all_running &= this->binary_sensor_service_ == nullptr || this->binary_sensor_service_->is_running(); +#endif +#ifdef USE_COVER + all_running &= this->cover_service_ == nullptr || this->cover_service_->is_running(); +#endif +#ifdef USE_FAN + all_running &= this->fan_service_ == nullptr || this->fan_service_->is_running(); +#endif +#ifdef USE_LIGHT + all_running &= this->light_service_ == nullptr || this->light_service_->is_running(); +#endif +#ifdef USE_SENSOR + all_running &= this->sensor_service_ == nullptr || this->sensor_service_->is_running(); +#endif +#ifdef USE_SWITCH + all_running &= this->switch_service_ == nullptr || this->switch_service_->is_running(); +#endif +#ifdef USE_TEXT_SENSOR + all_running &= this->text_sensor_service_ == nullptr || this->text_sensor_service_->is_running(); +#endif +#ifdef USE_CLIMATE + all_running &= this->climate_service_ == nullptr || this->climate_service_->is_running(); +#endif + if (all_running) { + ESP_LOGD(TAG, "BLE Controller started"); + this->state_ = RUNNING; +#ifdef USE_LOGGER + logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { + if (level > this->log_level_) + return; + std::string log; + log += "["; + log += tag; + log += "] "; + log += message; + this->logger_characteristic_->set_value(log); + this->logger_characteristic_->notify(); + }); +#endif + } else { + this->esphome_service_->start(); +#ifdef USE_BINARY_SENSOR + this->binary_sensor_service_->start(); +#endif +#ifdef USE_COVER + this->cover_service_->start(); +#endif +#ifdef USE_FAN + this->fan_service_->start(); +#endif +#ifdef USE_LIGHT + this->light_service_->start(); +#endif +#ifdef USE_SENSOR + this->sensor_service_->start(); +#endif +#ifdef USE_SWITCH + this->switch_service_->start(); +#endif +#ifdef USE_TEXT_SENSOR + this->text_sensor_service_->start(); +#endif +#ifdef USE_CLIMATE + this->climate_service_->start(); +#endif + } + break; + } + + case RUNNING: + case INIT: + break; + default: + break; + } +} + +void BLEController::start() { + if (this->state_ == RUNNING) + return; + this->state_ = STARTING; +} +void BLEController::stop() { + this->esphome_service_->stop(); +#ifdef USE_BINARY_SENSOR + this->binary_sensor_service_->stop(); +#endif +#ifdef USE_COVER + this->cover_service_->stop(); +#endif +#ifdef USE_FAN + this->fan_service_->stop(); +#endif +#ifdef USE_LIGHT + this->light_service_->stop(); +#endif +#ifdef USE_SENSOR + this->sensor_service_->stop(); +#endif +#ifdef USE_SWITCH + this->switch_service_->stop(); +#endif +#ifdef USE_TEXT_SENSOR + this->text_sensor_service_->stop(); +#endif +#ifdef USE_CLIMATE + this->climate_service_->stop(); +#endif +} +float BLEController::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } + +#ifdef USE_BINARY_SENSOR +void BLEController::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { + if (obj->is_internal()) + return; + auto *characteristic = this->characteristics_[obj->get_object_id_hash()]; + characteristic->set_value(state); + characteristic->notify(); +} +#endif +#ifdef USE_COVER +void BLEController::on_cover_update(cover::Cover *obj) { + if (obj->is_internal()) + return; +} +#endif +#ifdef USE_FAN +void BLEController::on_fan_update(fan::FanState *obj) { + if (obj->is_internal()) + return; +} +#endif +#ifdef USE_LIGHT +void BLEController::on_light_update(light::LightState *obj) { + if (obj->is_internal()) + return; +} +#endif +#ifdef USE_SENSOR +void BLEController::on_sensor_update(sensor::Sensor *obj, float state) { + if (obj->is_internal()) + return; + auto *characteristic = this->characteristics_[obj->get_object_id_hash()]; + characteristic->set_value(state); + characteristic->notify(); +} +#endif +#ifdef USE_SWITCH +void BLEController::on_switch_update(switch_::Switch *obj, bool state) { + if (obj->is_internal()) + return; + auto *characteristic = this->characteristics_[obj->get_object_id_hash()]; + characteristic->set_value(state); + characteristic->notify(); +} +#endif +#ifdef USE_TEXT_SENSOR +void BLEController::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { + if (obj->is_internal()) + return; + auto *characteristic = this->characteristics_[obj->get_object_id_hash()]; + characteristic->set_value(state); + characteristic->notify(); +} +#endif +#ifdef USE_CLIMATE +void BLEController::on_climate_update(climate::Climate *obj) { + if (obj->is_internal()) + return; +} +#endif + +} // namespace esp32_ble_controller +} // namespace esphome + +#endif diff --git a/esphome/components/esp32_ble_controller/ble_controller.h b/esphome/components/esp32_ble_controller/ble_controller.h new file mode 100644 index 0000000000..ec08224736 --- /dev/null +++ b/esphome/components/esp32_ble_controller/ble_controller.h @@ -0,0 +1,106 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/controller.h" +#include "esphome/components/esp32_ble_server/ble_2901.h" +#include "esphome/components/esp32_ble_server/ble_2902.h" +#include "esphome/components/esp32_ble_server/ble_characteristic.h" +#include "esphome/components/esp32_ble_server/ble_server.h" +#include + +#ifdef USE_LOGGER +#include "esphome/core/log.h" +#endif + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace esp32_ble_controller { + +using namespace esp32_ble_server; + +class BLEController : public Component, public Controller, public esp32_ble_server::BLEServiceComponent { + public: + void loop() override; + void setup() override; + void start() override; + void stop() override; + + float get_setup_priority() const override; + +#ifdef USE_LOGGER + void set_log_level(int level) { this->log_level_ = level; } +#endif + +#ifdef USE_BINARY_SENSOR + void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; +#endif +#ifdef USE_COVER + void on_cover_update(cover::Cover *obj) override; +#endif +#ifdef USE_FAN + void on_fan_update(fan::FanState *obj) override; +#endif +#ifdef USE_LIGHT + void on_light_update(light::LightState *obj) override; +#endif +#ifdef USE_SENSOR + void on_sensor_update(sensor::Sensor *obj, float state) override; +#endif +#ifdef USE_SWITCH + void on_switch_update(switch_::Switch *obj, bool state) override; +#endif +#ifdef USE_TEXT_SENSOR + void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override; +#endif +#ifdef USE_CLIMATE + void on_climate_update(climate::Climate *obj) override; +#endif + + protected: + enum State : uint8_t { + FAILED = 0x00, + INIT, + CREATING, + STARTING, + RUNNING, + } state_{INIT}; + + std::map characteristics_; + + BLEService *esphome_service_; +#ifdef USE_LOGGER + BLECharacteristic *logger_characteristic_; + int log_level_{ESPHOME_LOG_LEVEL_DEBUG}; +#endif + +#ifdef USE_BINARY_SENSOR + BLEService *binary_sensor_service_; +#endif +#ifdef USE_COVER + BLEService *cover_service_; +#endif +#ifdef USE_FAN + BLEService *fan_service_; +#endif +#ifdef USE_LIGHT + BLEService *light_service_; +#endif +#ifdef USE_SENSOR + BLEService *sensor_service_; +#endif +#ifdef USE_SWITCH + BLEService *switch_service_; +#endif +#ifdef USE_TEXT_SENSOR + BLEService *text_sensor_service_; +#endif +#ifdef USE_CLIMATE + BLEService *climate_service_; +#endif +}; + +} // namespace esp32_ble_controller +} // namespace esphome + +#endif diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index db83eb6bee..2b4aea8ae0 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -61,14 +61,11 @@ void BLEServer::loop() { break; } case STARTING_SERVICE: { - if (!this->device_information_service_->is_created()) { - break; - } if (this->device_information_service_->is_running()) { this->state_ = RUNNING; this->can_proceed_ = true; ESP_LOGD(TAG, "BLE server setup successfully"); - } else if (!this->device_information_service_->is_starting()) { + } else if (this->device_information_service_->is_created() && !this->device_information_service_->is_starting()) { this->device_information_service_->start(); } break; diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index b7c88a436c..563ee723af 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -71,9 +71,14 @@ bool BLEService::do_create_characteristics_() { } void BLEService::start() { + if (this->running_state_ == RUNNING) + return; + if (this->do_create_characteristics_()) return; + ESP_LOGD(TAG, "Starting BLE service %s", this->uuid_.to_string().c_str()); + esp_err_t err = esp_ble_gatts_start_service(this->handle_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gatts_start_service failed: %d", err); @@ -111,12 +116,14 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g if (this->uuid_ == ESPBTUUID::from_uuid(param->create.service_id.id.uuid) && this->inst_id_ == param->create.service_id.id.inst_id) { this->handle_ = param->create.service_handle; + ESP_LOGI(TAG, "Service %s created", this->uuid_.to_string().c_str()); this->init_state_ = CREATED; } break; } case ESP_GATTS_START_EVT: { if (param->start.service_handle == this->handle_) { + ESP_LOGI(TAG, "Service %s started", this->uuid_.to_string().c_str()); this->running_state_ = RUNNING; } break; diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 4c337055cb..c9890455d6 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor, output, esp32_ble_server -from esphome.const import CONF_ID, ESP_PLATFORM_ESP32 +from esphome.const import CONF_BLE_SERVER_ID, CONF_ID, ESP_PLATFORM_ESP32 AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"] @@ -12,7 +12,6 @@ ESP_PLATFORMS = [ESP_PLATFORM_ESP32] CONF_AUTHORIZED_DURATION = "authorized_duration" CONF_AUTHORIZER = "authorizer" -CONF_BLE_SERVER_ID = "ble_server_id" CONF_IDENTIFY_DURATION = "identify_duration" CONF_STATUS_INDICATOR = "status_indicator" CONF_WIFI_TIMEOUT = "wifi_timeout" diff --git a/esphome/const.py b/esphome/const.py index b50b60a204..11759d82ef 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -86,6 +86,7 @@ CONF_BINARY_SENSORS = "binary_sensors" CONF_BINDKEY = "bindkey" CONF_BIRTH_MESSAGE = "birth_message" CONF_BIT_DEPTH = "bit_depth" +CONF_BLE_SERVER_ID = "ble_server_id" CONF_BLUE = "blue" CONF_BOARD = "board" CONF_BOARD_FLASH_MODE = "board_flash_mode"