mirror of
https://github.com/esphome/esphome.git
synced 2024-11-28 12:46:22 +01:00
commit
a57a842f7b
@ -20,14 +20,13 @@ void ESP32BLE::setup() {
|
||||
global_ble = this;
|
||||
ESP_LOGCONFIG(TAG, "Setting up BLE...");
|
||||
|
||||
xTaskCreatePinnedToCore(ESP32BLE::ble_core_task_,
|
||||
"ble_task", // name
|
||||
10000, // stack size
|
||||
nullptr, // input params
|
||||
1, // priority
|
||||
nullptr, // handle, not needed
|
||||
0 // core
|
||||
);
|
||||
if (!ble_setup_()) {
|
||||
ESP_LOGE(TAG, "BLE could not be set up");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "BLE setup complete");
|
||||
}
|
||||
|
||||
void ESP32BLE::mark_failed() {
|
||||
@ -37,23 +36,6 @@ void ESP32BLE::mark_failed() {
|
||||
}
|
||||
}
|
||||
|
||||
bool ESP32BLE::can_proceed() { return this->ready_; }
|
||||
|
||||
void ESP32BLE::ble_core_task_(void *params) {
|
||||
if (!ble_setup_()) {
|
||||
ESP_LOGE(TAG, "BLE could not be set up");
|
||||
global_ble->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
global_ble->ready_ = true;
|
||||
ESP_LOGD(TAG, "BLE Setup complete");
|
||||
|
||||
while (true) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
bool ESP32BLE::ble_setup_() {
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err != ESP_OK) {
|
||||
@ -84,7 +66,7 @@ bool ESP32BLE::ble_setup_() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (global_ble->has_server()) {
|
||||
if (this->has_server()) {
|
||||
err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gatts_register_callback failed: %d", err);
|
||||
@ -92,7 +74,7 @@ bool ESP32BLE::ble_setup_() {
|
||||
}
|
||||
}
|
||||
|
||||
if (global_ble->has_client()) {
|
||||
if (this->has_client()) {
|
||||
err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err);
|
||||
@ -119,27 +101,28 @@ bool ESP32BLE::ble_setup_() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// void ESP32BLE::loop() {
|
||||
// BLEEvent *ble_event = this->ble_events_.pop();
|
||||
// while (ble_event != nullptr) {
|
||||
// switch (ble_event->type_) {
|
||||
// case ble_event->GATTS:
|
||||
// this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if,
|
||||
// &ble_event->event_.gatts.gatts_param);
|
||||
// break;
|
||||
// case ble_event->GAP:
|
||||
// this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// delete ble_event;
|
||||
// ble_event = this->ble_events_.pop();
|
||||
// }
|
||||
// }
|
||||
void ESP32BLE::loop() {
|
||||
BLEEvent *ble_event = this->ble_events_.pop();
|
||||
while (ble_event != nullptr) {
|
||||
switch (ble_event->type_) {
|
||||
case ble_event->GATTS:
|
||||
this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if,
|
||||
&ble_event->event_.gatts.gatts_param);
|
||||
break;
|
||||
case ble_event->GAP:
|
||||
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
delete ble_event;
|
||||
ble_event = this->ble_events_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
global_ble->real_gap_event_handler_(event, param);
|
||||
BLEEvent *new_event = new BLEEvent(event, param);
|
||||
global_ble->ble_events_.push(new_event);
|
||||
}
|
||||
|
||||
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
@ -152,7 +135,8 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap
|
||||
|
||||
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) {
|
||||
global_ble->real_gatts_event_handler_(event, gatts_if, param);
|
||||
BLEEvent *new_event = new BLEEvent(event, gatts_if, param);
|
||||
global_ble->ble_events_.push(new_event);
|
||||
}
|
||||
|
||||
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
|
@ -23,17 +23,14 @@ typedef struct {
|
||||
class ESP32BLE : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
// void loop() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void mark_failed() override;
|
||||
bool can_proceed() override;
|
||||
|
||||
bool has_server() { return this->server_ != nullptr; }
|
||||
bool has_client() { return false; }
|
||||
|
||||
bool is_ready() { return this->ready_; }
|
||||
|
||||
void set_server(BLEServer *server) { this->server_ = server; }
|
||||
|
||||
protected:
|
||||
@ -45,10 +42,7 @@ class ESP32BLE : public Component {
|
||||
void real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
|
||||
static void ble_core_task_(void *params);
|
||||
static bool ble_setup_();
|
||||
|
||||
bool ready_{false};
|
||||
bool ble_setup_();
|
||||
|
||||
BLEServer *server_{nullptr};
|
||||
Queue<BLEEvent> ble_events_;
|
||||
|
@ -13,10 +13,8 @@ static const char *TAG = "esp32_ble.characteristic";
|
||||
|
||||
BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) {
|
||||
this->set_value_lock_ = xSemaphoreCreateBinary();
|
||||
this->create_lock_ = xSemaphoreCreateBinary();
|
||||
|
||||
xSemaphoreGive(this->set_value_lock_);
|
||||
xSemaphoreGive(this->create_lock_);
|
||||
|
||||
this->properties_ = (esp_gatt_char_prop_t) 0;
|
||||
|
||||
this->set_broadcast_property((properties & PROPERTY_BROADCAST) != 0);
|
||||
@ -100,12 +98,11 @@ void BLECharacteristic::notify(bool notification) {
|
||||
|
||||
void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { this->descriptors_.push_back(descriptor); }
|
||||
|
||||
bool BLECharacteristic::do_create(BLEService *service) {
|
||||
void BLECharacteristic::do_create(BLEService *service) {
|
||||
this->service_ = service;
|
||||
esp_attr_control_t control;
|
||||
control.auto_rsp = ESP_GATT_RSP_BY_APP;
|
||||
|
||||
xSemaphoreTake(this->create_lock_, portMAX_DELAY);
|
||||
ESP_LOGV(TAG, "Creating characteristic - %s", this->uuid_.to_string().c_str());
|
||||
|
||||
esp_bt_uuid_t uuid = this->uuid_.get_uuid();
|
||||
@ -114,15 +111,39 @@ bool BLECharacteristic::do_create(BLEService *service) {
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gatts_add_char failed: %d", err);
|
||||
return;
|
||||
}
|
||||
|
||||
this->state_ = CREATING;
|
||||
}
|
||||
|
||||
bool BLECharacteristic::is_created() {
|
||||
if (this->state_ == CREATED)
|
||||
return true;
|
||||
|
||||
if (this->state_ != CREATING_DEPENDENTS)
|
||||
return false;
|
||||
}
|
||||
|
||||
xSemaphoreWait(this->create_lock_, portMAX_DELAY);
|
||||
|
||||
bool created = true;
|
||||
for (auto *descriptor : this->descriptors_) {
|
||||
descriptor->do_create(this);
|
||||
created &= descriptor->is_created();
|
||||
}
|
||||
return true;
|
||||
if (created)
|
||||
this->state_ = CREATED;
|
||||
return this->state_ == CREATED;
|
||||
}
|
||||
|
||||
bool BLECharacteristic::is_failed() {
|
||||
if (this->state_ == FAILED)
|
||||
return true;
|
||||
|
||||
bool failed = false;
|
||||
for (auto *descriptor : this->descriptors_) {
|
||||
failed |= descriptor->is_failed();
|
||||
}
|
||||
if (failed)
|
||||
this->state_ = FAILED;
|
||||
return this->state_ == FAILED;
|
||||
}
|
||||
|
||||
void BLECharacteristic::set_broadcast_property(bool value) {
|
||||
@ -168,7 +189,12 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
|
||||
case ESP_GATTS_ADD_CHAR_EVT: {
|
||||
if (this->uuid_ == ESPBTUUID::from_uuid(param->add_char.char_uuid)) {
|
||||
this->handle_ = param->add_char.attr_handle;
|
||||
xSemaphoreGive(this->create_lock_);
|
||||
|
||||
for (auto *descriptor : this->descriptors_) {
|
||||
descriptor->do_create(this);
|
||||
}
|
||||
|
||||
this->state_ = CREATING_DEPENDENTS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -42,10 +42,10 @@ class BLECharacteristic {
|
||||
|
||||
void notify(bool notification = true);
|
||||
|
||||
bool do_create(BLEService *service);
|
||||
void do_create(BLEService *service);
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
void on_write(const std::function<void(const std::vector<uint8_t> &)> &func) { this->on_write_ = func; }
|
||||
void on_write(const std::function<void(const std::vector<uint8_t> &)> &&func) { this->on_write_ = std::move(func); }
|
||||
|
||||
void add_descriptor(BLEDescriptor *descriptor);
|
||||
|
||||
@ -60,6 +60,9 @@ class BLECharacteristic {
|
||||
static const uint32_t PROPERTY_INDICATE = 1 << 4;
|
||||
static const uint32_t PROPERTY_WRITE_NR = 1 << 5;
|
||||
|
||||
bool is_created();
|
||||
bool is_failed();
|
||||
|
||||
protected:
|
||||
bool write_event_{false};
|
||||
BLEService *service_;
|
||||
@ -70,13 +73,20 @@ class BLECharacteristic {
|
||||
uint16_t value_read_offset_{0};
|
||||
std::vector<uint8_t> value_;
|
||||
SemaphoreHandle_t set_value_lock_;
|
||||
SemaphoreHandle_t create_lock_;
|
||||
|
||||
std::vector<BLEDescriptor *> descriptors_;
|
||||
|
||||
std::function<void(const std::vector<uint8_t> &)> on_write_;
|
||||
|
||||
esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
|
||||
|
||||
enum State : uint8_t {
|
||||
FAILED = 0x00,
|
||||
INIT,
|
||||
CREATING,
|
||||
CREATING_DEPENDENTS,
|
||||
CREATED,
|
||||
} state_{INIT};
|
||||
};
|
||||
|
||||
} // namespace esp32_ble
|
||||
|
@ -16,30 +16,25 @@ BLEDescriptor::BLEDescriptor(ESPBTUUID uuid, uint16_t max_len) {
|
||||
this->value_.attr_len = 0;
|
||||
this->value_.attr_max_len = max_len;
|
||||
this->value_.attr_value = (uint8_t *) malloc(max_len);
|
||||
|
||||
this->create_lock_ = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(this->create_lock_);
|
||||
}
|
||||
|
||||
BLEDescriptor::~BLEDescriptor() { free(this->value_.attr_value); }
|
||||
|
||||
bool BLEDescriptor::do_create(BLECharacteristic *characteristic) {
|
||||
void BLEDescriptor::do_create(BLECharacteristic *characteristic) {
|
||||
this->characteristic_ = characteristic;
|
||||
esp_attr_control_t control;
|
||||
control.auto_rsp = ESP_GATT_AUTO_RSP;
|
||||
|
||||
xSemaphoreTake(this->create_lock_, portMAX_DELAY);
|
||||
ESP_LOGV(TAG, "Creating descriptor - %s", this->uuid_.to_string().c_str());
|
||||
esp_bt_uuid_t uuid = this->uuid_.get_uuid();
|
||||
esp_err_t err = esp_ble_gatts_add_char_descr(this->characteristic_->get_service()->get_handle(), &uuid,
|
||||
this->permissions_, &this->value_, &control);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gatts_add_char_descr failed: %d", err);
|
||||
return false;
|
||||
this->state_ = FAILED;
|
||||
return;
|
||||
}
|
||||
xSemaphoreWait(this->create_lock_, portMAX_DELAY);
|
||||
|
||||
return true;
|
||||
this->state_ = CREATING;
|
||||
}
|
||||
|
||||
void BLEDescriptor::set_value(const std::string &value) { this->set_value((uint8_t *) value.data(), value.length()); }
|
||||
@ -60,7 +55,7 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
|
||||
this->characteristic_->get_service()->get_handle() == param->add_char_descr.service_handle &&
|
||||
this->characteristic_ == this->characteristic_->get_service()->get_last_created_characteristic()) {
|
||||
this->handle_ = param->add_char_descr.attr_handle;
|
||||
xSemaphoreGive(this->create_lock_);
|
||||
this->state_ = CREATED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -16,22 +16,31 @@ class BLEDescriptor {
|
||||
public:
|
||||
BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100);
|
||||
virtual ~BLEDescriptor();
|
||||
bool do_create(BLECharacteristic *characteristic);
|
||||
void do_create(BLECharacteristic *characteristic);
|
||||
|
||||
void set_value(const std::string &value);
|
||||
void set_value(const uint8_t *data, size_t length);
|
||||
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
bool is_created() { return this->state_ == CREATED; }
|
||||
bool is_failed() { return this->state_ == FAILED; }
|
||||
|
||||
protected:
|
||||
BLECharacteristic *characteristic_{nullptr};
|
||||
ESPBTUUID uuid_;
|
||||
uint16_t handle_{0xFFFF};
|
||||
SemaphoreHandle_t create_lock_;
|
||||
|
||||
esp_attr_value_t value_;
|
||||
|
||||
esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
|
||||
|
||||
enum State : uint8_t {
|
||||
FAILED = 0x00,
|
||||
INIT,
|
||||
CREATING,
|
||||
CREATED,
|
||||
} state_{INIT};
|
||||
};
|
||||
|
||||
} // namespace esp32_ble
|
||||
|
@ -18,7 +18,7 @@ namespace esp32_ble {
|
||||
|
||||
static const char *TAG = "esp32_ble.server";
|
||||
|
||||
static const uint16_t DEVICE_INFORMATION_SERVICE_UUID = 0x180A;
|
||||
static const uint16_t device_information_service__UUID = 0x180A;
|
||||
static const uint16_t MODEL_UUID = 0x2A24;
|
||||
static const uint16_t VERSION_UUID = 0x2A26;
|
||||
static const uint16_t MANUFACTURER_UUID = 0x2A29;
|
||||
@ -32,59 +32,78 @@ void BLEServer::setup() {
|
||||
ESP_LOGD(TAG, "Setting up BLE Server...");
|
||||
|
||||
global_ble_server = this;
|
||||
this->register_lock_ = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(this->register_lock_);
|
||||
|
||||
this->advertising_ = new BLEAdvertising();
|
||||
|
||||
this->setup_server_();
|
||||
|
||||
for (auto *component : this->service_components_) {
|
||||
component->setup_service();
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "BLE Server set up complete...");
|
||||
}
|
||||
|
||||
void BLEServer::setup_server_() {
|
||||
xSemaphoreTake(this->register_lock_, portMAX_DELAY);
|
||||
esp_err_t err = esp_ble_gatts_app_register(0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gatts_app_register failed: %d", err);
|
||||
this->mark_failed();
|
||||
return;
|
||||
void BLEServer::loop() {
|
||||
switch (this->state_) {
|
||||
case RUNNING:
|
||||
return;
|
||||
|
||||
case INIT: {
|
||||
esp_err_t err = esp_ble_gatts_app_register(0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gatts_app_register failed: %d", err);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->state_ = REGISTERING;
|
||||
break;
|
||||
}
|
||||
case REGISTERING: {
|
||||
if (this->registered_) {
|
||||
this->device_information_service_ = this->create_service(device_information_service__UUID);
|
||||
|
||||
this->create_device_characteristics_();
|
||||
|
||||
this->advertising_->set_scan_response(true);
|
||||
this->advertising_->set_min_preferred_interval(0x06);
|
||||
this->advertising_->start();
|
||||
|
||||
this->state_ = STARTING_SERVICE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STARTING_SERVICE: {
|
||||
if (this->device_information_service_->is_running()) {
|
||||
for (auto *component : this->service_components_) {
|
||||
component->setup_service();
|
||||
}
|
||||
this->state_ = SETTING_UP_COMPONENT_SERVICES;
|
||||
} else if (!this->device_information_service_->is_starting()) {
|
||||
this->device_information_service_->start();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SETTING_UP_COMPONENT_SERVICES: {
|
||||
this->state_ = RUNNING;
|
||||
this->can_proceed_ = true;
|
||||
ESP_LOGD(TAG, "BLE server setup successfully");
|
||||
break;
|
||||
}
|
||||
}
|
||||
xSemaphoreWait(this->register_lock_, portMAX_DELAY);
|
||||
|
||||
this->device_information_service = this->create_service(DEVICE_INFORMATION_SERVICE_UUID);
|
||||
|
||||
this->create_device_characteristics_();
|
||||
|
||||
this->advertising_->set_scan_response(true);
|
||||
this->advertising_->set_min_preferred_interval(0x06);
|
||||
this->advertising_->start();
|
||||
|
||||
this->device_information_service->start();
|
||||
}
|
||||
|
||||
bool BLEServer::create_device_characteristics_() {
|
||||
if (this->model_.has_value()) {
|
||||
BLECharacteristic *model =
|
||||
this->device_information_service->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
model->set_value(this->model_.value());
|
||||
} else {
|
||||
#ifdef ARDUINO_BOARD
|
||||
BLECharacteristic *model =
|
||||
this->device_information_service->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
model->set_value(ARDUINO_BOARD);
|
||||
#endif
|
||||
}
|
||||
|
||||
BLECharacteristic *version =
|
||||
this->device_information_service->create_characteristic(VERSION_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
this->device_information_service_->create_characteristic(VERSION_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
version->set_value("ESPHome " ESPHOME_VERSION);
|
||||
|
||||
BLECharacteristic *manufacturer =
|
||||
this->device_information_service->create_characteristic(MANUFACTURER_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
this->device_information_service_->create_characteristic(MANUFACTURER_UUID, BLECharacteristic::PROPERTY_READ);
|
||||
manufacturer->set_value(this->manufacturer_);
|
||||
|
||||
return true;
|
||||
@ -134,7 +153,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga
|
||||
}
|
||||
case ESP_GATTS_REG_EVT: {
|
||||
this->gatts_if_ = gatts_if;
|
||||
xSemaphoreGive(this->register_lock_);
|
||||
this->registered_ = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -24,13 +24,17 @@ class BLEServiceComponent {
|
||||
virtual void setup_service();
|
||||
virtual void on_client_connect(){};
|
||||
virtual void on_client_disconnect(){};
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
};
|
||||
|
||||
class BLEServer : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
bool can_proceed() override { return this->can_proceed_; }
|
||||
|
||||
void teardown();
|
||||
|
||||
@ -53,27 +57,35 @@ class BLEServer : public Component {
|
||||
|
||||
protected:
|
||||
bool create_device_characteristics_();
|
||||
void setup_server_();
|
||||
|
||||
void add_client_(uint16_t conn_id, void *client) {
|
||||
this->clients_.insert(std::pair<uint16_t, void *>(conn_id, client));
|
||||
}
|
||||
bool remove_client_(uint16_t conn_id) { return this->clients_.erase(conn_id) > 0; }
|
||||
|
||||
bool can_proceed_{false};
|
||||
|
||||
std::string manufacturer_;
|
||||
optional<std::string> model_;
|
||||
esp_gatt_if_t gatts_if_{0};
|
||||
bool registered_{false};
|
||||
BLEAdvertising *advertising_;
|
||||
|
||||
uint32_t connected_clients_{0};
|
||||
std::map<uint16_t, void *> clients_;
|
||||
|
||||
std::vector<BLEService *> services_;
|
||||
BLEService *device_information_service;
|
||||
BLEService *device_information_service_;
|
||||
|
||||
std::vector<BLEServiceComponent *> service_components_;
|
||||
|
||||
SemaphoreHandle_t register_lock_;
|
||||
enum State : uint8_t {
|
||||
INIT = 0x00,
|
||||
REGISTERING,
|
||||
STARTING_SERVICE,
|
||||
SETTING_UP_COMPONENT_SERVICES,
|
||||
RUNNING,
|
||||
} state_{INIT};
|
||||
};
|
||||
|
||||
extern BLEServer *global_ble_server;
|
||||
|
@ -10,15 +10,7 @@ namespace esp32_ble {
|
||||
static const char *TAG = "esp32_ble.service";
|
||||
|
||||
BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id)
|
||||
: uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) {
|
||||
this->create_lock_ = xSemaphoreCreateBinary();
|
||||
this->start_lock_ = xSemaphoreCreateBinary();
|
||||
this->stop_lock_ = xSemaphoreCreateBinary();
|
||||
|
||||
xSemaphoreGive(this->create_lock_);
|
||||
xSemaphoreGive(this->start_lock_);
|
||||
xSemaphoreGive(this->stop_lock_);
|
||||
}
|
||||
: uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) {}
|
||||
|
||||
BLEService::~BLEService() {
|
||||
for (auto &chr : this->characteristics_)
|
||||
@ -47,10 +39,9 @@ BLECharacteristic *BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_ch
|
||||
return characteristic;
|
||||
}
|
||||
|
||||
bool BLEService::do_create(BLEServer *server) {
|
||||
void BLEService::do_create(BLEServer *server) {
|
||||
this->server_ = server;
|
||||
|
||||
xSemaphoreTake(this->create_lock_, portMAX_DELAY);
|
||||
esp_gatt_srvc_id_t srvc_id;
|
||||
srvc_id.is_primary = true;
|
||||
srvc_id.id.inst_id = this->inst_id_;
|
||||
@ -59,36 +50,58 @@ bool BLEService::do_create(BLEServer *server) {
|
||||
esp_err_t err = esp_ble_gatts_create_service(server->get_gatts_if(), &srvc_id, this->num_handles_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gatts_create_service failed: %d", err);
|
||||
return false;
|
||||
this->init_state_ = FAILED;
|
||||
return;
|
||||
}
|
||||
xSemaphoreWait(this->create_lock_, portMAX_DELAY);
|
||||
this->init_state_ = CREATING;
|
||||
}
|
||||
|
||||
bool BLEService::do_create_characteristics_() {
|
||||
if (this->created_characteristic_count_ >= this->characteristics_.size() &&
|
||||
(this->last_created_characteristic_ == nullptr || this->last_created_characteristic_->is_created()))
|
||||
return false; // Signifies there are no characteristics, or they are all finished being created.
|
||||
|
||||
if (this->last_created_characteristic_ != nullptr && !this->last_created_characteristic_->is_created())
|
||||
return true; // Signifies that the previous characteristic is still being created.
|
||||
|
||||
auto *characteristic = this->characteristics_[this->created_characteristic_count_++];
|
||||
this->last_created_characteristic_ = characteristic;
|
||||
characteristic->do_create(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void BLEService::start() {
|
||||
for (auto *characteristic : this->characteristics_) {
|
||||
this->last_created_characteristic_ = characteristic;
|
||||
characteristic->do_create(this);
|
||||
}
|
||||
if (this->do_create_characteristics_())
|
||||
return;
|
||||
|
||||
xSemaphoreTake(this->start_lock_, portMAX_DELAY);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
xSemaphoreWait(this->start_lock_, portMAX_DELAY);
|
||||
this->running_state_ = STARTING;
|
||||
}
|
||||
|
||||
void BLEService::stop() {
|
||||
xSemaphoreTake(this->stop_lock_, portMAX_DELAY);
|
||||
esp_err_t err = esp_ble_gatts_stop_service(this->handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gatts_stop_service failed: %d", err);
|
||||
return;
|
||||
}
|
||||
xSemaphoreWait(this->stop_lock_, portMAX_DELAY);
|
||||
this->running_state_ = STOPPING;
|
||||
}
|
||||
|
||||
bool BLEService::is_created() { return this->init_state_ == CREATED; }
|
||||
bool BLEService::is_failed() {
|
||||
if (this->init_state_ == FAILED)
|
||||
return true;
|
||||
bool failed = false;
|
||||
for (auto *characteristic : this->characteristics_)
|
||||
failed |= characteristic->is_failed();
|
||||
|
||||
if (failed)
|
||||
this->init_state_ = FAILED;
|
||||
return this->init_state_ == FAILED;
|
||||
}
|
||||
|
||||
void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
@ -98,19 +111,19 @@ 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;
|
||||
xSemaphoreGive(this->create_lock_);
|
||||
this->init_state_ = CREATED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_START_EVT: {
|
||||
if (param->start.service_handle == this->handle_) {
|
||||
xSemaphoreGive(this->start_lock_);
|
||||
this->running_state_ = RUNNING;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_STOP_EVT: {
|
||||
if (param->start.service_handle == this->handle_) {
|
||||
xSemaphoreGive(this->stop_lock_);
|
||||
this->running_state_ = STOPPED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -33,26 +33,44 @@ class BLEService {
|
||||
|
||||
BLEServer *get_server() { return this->server_; }
|
||||
|
||||
bool do_create(BLEServer *server);
|
||||
void do_create(BLEServer *server);
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
bool errored_{false};
|
||||
bool is_created();
|
||||
bool is_failed();
|
||||
|
||||
bool is_running() { return this->running_state_ == RUNNING; }
|
||||
bool is_starting() { return this->running_state_ == STARTING; }
|
||||
|
||||
protected:
|
||||
std::vector<BLECharacteristic *> characteristics_;
|
||||
BLECharacteristic *last_created_characteristic_{nullptr};
|
||||
uint32_t created_characteristic_count_{0};
|
||||
BLEServer *server_;
|
||||
ESPBTUUID uuid_;
|
||||
uint16_t num_handles_;
|
||||
uint16_t handle_{0xFFFF};
|
||||
uint8_t inst_id_;
|
||||
|
||||
SemaphoreHandle_t create_lock_;
|
||||
SemaphoreHandle_t start_lock_;
|
||||
SemaphoreHandle_t stop_lock_;
|
||||
bool do_create_characteristics_();
|
||||
|
||||
enum InitState : uint8_t {
|
||||
FAILED = 0x00,
|
||||
INIT,
|
||||
CREATING,
|
||||
CREATING_DEPENDENTS,
|
||||
CREATED,
|
||||
} init_state_{INIT};
|
||||
|
||||
enum RunningState : uint8_t {
|
||||
STARTING,
|
||||
RUNNING,
|
||||
STOPPING,
|
||||
STOPPED,
|
||||
} running_state_{STOPPED};
|
||||
};
|
||||
|
||||
} // namespace esp32_ble
|
||||
|
@ -53,7 +53,7 @@ template<class T> class Queue {
|
||||
SemaphoreHandle_t m;
|
||||
};
|
||||
|
||||
// Received GAP and GATTC events are only queued, and get processed in the main loop().
|
||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||
// This class stores each event in a single type.
|
||||
class BLEEvent {
|
||||
public:
|
||||
@ -68,9 +68,18 @@ class BLEEvent {
|
||||
this->event_.gattc.gattc_if = i;
|
||||
memcpy(&this->event_.gattc.gattc_param, p, sizeof(esp_ble_gattc_cb_param_t));
|
||||
// Need to also make a copy of notify event data.
|
||||
if (e == ESP_GATTC_NOTIFY_EVT) {
|
||||
memcpy(this->event_.gattc.notify_data, p->notify.value, p->notify.value_len);
|
||||
this->event_.gattc.gattc_param.notify.value = this->event_.gattc.notify_data;
|
||||
switch (e) {
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len);
|
||||
this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data;
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
memcpy(this->event_.gattc.data, p->read.value, p->read.value_len);
|
||||
this->event_.gattc.gattc_param.read.value = this->event_.gattc.data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = GATTC;
|
||||
};
|
||||
@ -79,6 +88,15 @@ class BLEEvent {
|
||||
this->event_.gatts.gatts_event = e;
|
||||
this->event_.gatts.gatts_if = i;
|
||||
memcpy(&this->event_.gatts.gatts_param, p, sizeof(esp_ble_gatts_cb_param_t));
|
||||
// Need to also make a copy of write data.
|
||||
switch (e) {
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
memcpy(this->event_.gatts.data, p->write.value, p->write.len);
|
||||
this->event_.gatts.gatts_param.write.value = this->event_.gatts.data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = GATTS;
|
||||
};
|
||||
|
||||
@ -92,13 +110,14 @@ class BLEEvent {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t gattc_param;
|
||||
uint8_t notify_data[64];
|
||||
uint8_t data[64];
|
||||
} gattc;
|
||||
|
||||
struct gatts_event {
|
||||
esp_gatts_cb_event_t gatts_event;
|
||||
esp_gatt_if_t gatts_if;
|
||||
esp_ble_gatts_cb_param_t gatts_param;
|
||||
uint8_t data[64];
|
||||
} gatts;
|
||||
} event_;
|
||||
enum ble_event_t : uint8_t {
|
||||
|
@ -14,7 +14,9 @@ ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
|
||||
|
||||
void ESP32ImprovComponent::setup_service() {
|
||||
this->service_ = esp32_ble::global_ble_server->create_service(improv::SERVICE_UUID, true);
|
||||
}
|
||||
|
||||
void ESP32ImprovComponent::setup_characteristics() {
|
||||
this->status_ = this->service_->create_characteristic(
|
||||
improv::STATUS_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | esp32_ble::BLECharacteristic::PROPERTY_NOTIFY);
|
||||
esp32_ble::BLEDescriptor *status_descriptor = new esp32_ble::BLE2902();
|
||||
@ -62,16 +64,21 @@ void ESP32ImprovComponent::loop() {
|
||||
if (this->status_indicator_ != nullptr)
|
||||
this->status_indicator_->turn_off();
|
||||
|
||||
if (this->service_->is_created() && !this->setup_complete_) {
|
||||
this->setup_characteristics();
|
||||
}
|
||||
|
||||
if (this->should_start_ && this->setup_complete_) {
|
||||
ESP_LOGD(TAG, "Starting Improv service...");
|
||||
if (this->service_->is_running()) {
|
||||
this->service_->get_server()->get_advertising()->start();
|
||||
|
||||
this->service_->start();
|
||||
this->service_->get_server()->get_advertising()->start();
|
||||
|
||||
this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
|
||||
this->set_error_(improv::ERROR_NONE);
|
||||
this->should_start_ = false;
|
||||
ESP_LOGD(TAG, "Service started!");
|
||||
this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
|
||||
this->set_error_(improv::ERROR_NONE);
|
||||
this->should_start_ = false;
|
||||
ESP_LOGD(TAG, "Service started!");
|
||||
} else {
|
||||
this->service_->start();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case improv::STATE_AWAITING_AUTHORIZATION: {
|
||||
@ -191,7 +198,7 @@ void ESP32ImprovComponent::start() {
|
||||
this->should_start_ = true;
|
||||
}
|
||||
|
||||
void ESP32ImprovComponent::end() {
|
||||
void ESP32ImprovComponent::stop() {
|
||||
this->set_timeout("end-service", 1000, [this] {
|
||||
this->service_->stop();
|
||||
this->set_state_(improv::STATE_STOPPED);
|
||||
|
@ -21,11 +21,12 @@ class ESP32ImprovComponent : public Component, public esp32_ble::BLEServiceCompo
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
void setup_service() override;
|
||||
void setup_characteristics();
|
||||
void on_client_disconnect() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
void start();
|
||||
void end();
|
||||
void start() override;
|
||||
void stop() override;
|
||||
bool is_active() const { return this->state_ != improv::STATE_STOPPED; }
|
||||
|
||||
void set_authorizer(binary_sensor::BinarySensor *authorizer) { this->authorizer_ = authorizer; }
|
||||
|
@ -407,7 +407,7 @@ LightColorValues LightCall::validate_() {
|
||||
this->color_temperature_.reset();
|
||||
}
|
||||
|
||||
// sets RGB to 100% if only White specified
|
||||
// If white channel is specified, set RGB to white color (when interlock is enabled)
|
||||
if (this->white_.has_value()) {
|
||||
if (traits.get_supports_color_interlock()) {
|
||||
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
|
||||
@ -415,7 +415,7 @@ LightColorValues LightCall::validate_() {
|
||||
this->green_ = optional<float>(1.0f);
|
||||
this->blue_ = optional<float>(1.0f);
|
||||
}
|
||||
// make white values binary aka 0.0f or 1.0f...this allows brightness to do its job
|
||||
// make white values binary aka 0.0f or 1.0f... this allows brightness to do its job
|
||||
if (*this->white_ > 0.0f) {
|
||||
this->white_ = optional<float>(1.0f);
|
||||
} else {
|
||||
@ -423,44 +423,27 @@ LightColorValues LightCall::validate_() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// White to 0% if (exclusively) setting any RGB value that isn't 255,255,255
|
||||
// If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled)
|
||||
else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
||||
if (traits.get_supports_color_interlock()) {
|
||||
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f &&
|
||||
traits.get_supports_rgb_white_value() && traits.get_supports_color_interlock()) {
|
||||
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) {
|
||||
this->white_ = optional<float>(1.0f);
|
||||
} else if (!this->white_.has_value() || !traits.get_supports_rgb_white_value()) {
|
||||
} else {
|
||||
this->white_ = optional<float>(0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if changing Kelvin alone, change to white light
|
||||
// If only a color temperature is specified, change to white light
|
||||
else if (this->color_temperature_.has_value()) {
|
||||
if (!traits.get_supports_color_interlock()) {
|
||||
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
|
||||
this->red_ = optional<float>(1.0f);
|
||||
this->green_ = optional<float>(1.0f);
|
||||
this->blue_ = optional<float>(1.0f);
|
||||
}
|
||||
}
|
||||
// if setting Kelvin from color (i.e. switching to white light), set White to 100%
|
||||
this->red_ = optional<float>(1.0f);
|
||||
this->green_ = optional<float>(1.0f);
|
||||
this->blue_ = optional<float>(1.0f);
|
||||
|
||||
// if setting color temperature from color (i.e. switching to white light), set White to 100%
|
||||
auto cv = this->parent_->remote_values;
|
||||
bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f;
|
||||
bool now_white = *this->red_ == 1.0f && *this->blue_ == 1.0f && *this->green_ == 1.0f;
|
||||
if (traits.get_supports_color_interlock()) {
|
||||
if (cv.get_white() < 1.0f) {
|
||||
this->white_ = optional<float>(1.0f);
|
||||
}
|
||||
|
||||
if (was_color && !this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
|
||||
this->red_ = optional<float>(1.0f);
|
||||
this->green_ = optional<float>(1.0f);
|
||||
this->blue_ = optional<float>(1.0f);
|
||||
}
|
||||
} else {
|
||||
if (!this->white_.has_value() && was_color && now_white) {
|
||||
this->white_ = optional<float>(1.0f);
|
||||
}
|
||||
if (traits.get_supports_color_interlock() || was_color) {
|
||||
this->white_ = optional<float>(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel"
|
||||
|
||||
MODELS = {
|
||||
"1.54in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN),
|
||||
"1.54inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN_V2),
|
||||
"2.13in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN),
|
||||
"2.13in-ttgo": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN),
|
||||
"2.13in-ttgo-b1": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B1),
|
||||
@ -67,7 +68,7 @@ def validate_full_update_every_only_type_a(value):
|
||||
if MODELS[value[CONF_MODEL]][0] != "a":
|
||||
raise cv.Invalid(
|
||||
"The 'full_update_every' option is only available for models "
|
||||
"'1.54in', '2.13in', '2.90in', and '2.90inV2'."
|
||||
"'1.54in', '1.54inV2', '2.13in', '2.90in', and '2.90inV2'."
|
||||
)
|
||||
return value
|
||||
|
||||
|
@ -210,6 +210,9 @@ void WaveshareEPaperTypeA::dump_config() {
|
||||
case WAVESHARE_EPAPER_1_54_IN:
|
||||
ESP_LOGCONFIG(TAG, " Model: 1.54in");
|
||||
break;
|
||||
case WAVESHARE_EPAPER_1_54_IN_V2:
|
||||
ESP_LOGCONFIG(TAG, " Model: 1.54inV2");
|
||||
break;
|
||||
case WAVESHARE_EPAPER_2_13_IN:
|
||||
ESP_LOGCONFIG(TAG, " Model: 2.13in");
|
||||
break;
|
||||
@ -334,7 +337,7 @@ void HOT WaveshareEPaperTypeA::display() {
|
||||
|
||||
// COMMAND DISPLAY UPDATE CONTROL 2
|
||||
this->command(0x22);
|
||||
if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2) {
|
||||
if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2 || this->model_ == WAVESHARE_EPAPER_1_54_IN_V2) {
|
||||
this->data(full_update ? 0xF7 : 0xFF);
|
||||
} else if (this->model_ == TTGO_EPAPER_2_13_IN_B73) {
|
||||
this->data(0xC7);
|
||||
@ -353,6 +356,8 @@ int WaveshareEPaperTypeA::get_width_internal() {
|
||||
switch (this->model_) {
|
||||
case WAVESHARE_EPAPER_1_54_IN:
|
||||
return 200;
|
||||
case WAVESHARE_EPAPER_1_54_IN_V2:
|
||||
return 200;
|
||||
case WAVESHARE_EPAPER_2_13_IN:
|
||||
return 128;
|
||||
case TTGO_EPAPER_2_13_IN:
|
||||
@ -372,6 +377,8 @@ int WaveshareEPaperTypeA::get_height_internal() {
|
||||
switch (this->model_) {
|
||||
case WAVESHARE_EPAPER_1_54_IN:
|
||||
return 200;
|
||||
case WAVESHARE_EPAPER_1_54_IN_V2:
|
||||
return 200;
|
||||
case WAVESHARE_EPAPER_2_13_IN:
|
||||
return 250;
|
||||
case TTGO_EPAPER_2_13_IN:
|
||||
|
@ -66,6 +66,7 @@ class WaveshareEPaper : public PollingComponent,
|
||||
|
||||
enum WaveshareEPaperTypeAModel {
|
||||
WAVESHARE_EPAPER_1_54_IN = 0,
|
||||
WAVESHARE_EPAPER_1_54_IN_V2,
|
||||
WAVESHARE_EPAPER_2_13_IN,
|
||||
WAVESHARE_EPAPER_2_9_IN,
|
||||
WAVESHARE_EPAPER_2_9_IN_V2,
|
||||
@ -85,7 +86,7 @@ class WaveshareEPaperTypeA : public WaveshareEPaper {
|
||||
void display() override;
|
||||
|
||||
void deep_sleep() override {
|
||||
if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2) {
|
||||
if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2 || this->model_ == WAVESHARE_EPAPER_1_54_IN_V2) {
|
||||
// COMMAND DEEP SLEEP MODE
|
||||
this->command(0x10);
|
||||
this->data(0x01);
|
||||
|
@ -508,7 +508,7 @@ void WiFiComponent::check_connecting_finished() {
|
||||
}
|
||||
#ifdef USE_IMPROV
|
||||
if (this->is_esp32_improv_active_()) {
|
||||
esp32_improv::global_improv_component->end();
|
||||
esp32_improv::global_improv_component->stop();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
MAJOR_VERSION = 1
|
||||
MINOR_VERSION = 19
|
||||
PATCH_VERSION = "0b1"
|
||||
PATCH_VERSION = "0b2"
|
||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user